[llvm-branch-commits] [clang] clang: Emit "exception-model" module flag (PR #220053)
Matt Arsenault via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Sep 5 05:01:05 PDT 2026
https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/220053
>From 5b5deda33f99b8079f7b51b207384958bbf1cfe9 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Thu, 30 Jul 2026 23:34:49 +0200
Subject: [PATCH 1/2] clang: Emit "exception-model" module flag
Record the exception-handling model as an "exception-model" IR module
flag when it differs from the target triple's default, mirroring how
other target ABI properties are recorded. Adds a
CodeGenOptions::toExceptionHandling helper to translate clang's
ExceptionHandlingKind into the LLVM ExceptionHandling enum.
Co-authored-by: Claude (Claude-Opus-4.8) <noreply at anthropic.com>
---
clang/include/clang/Basic/CodeGenOptions.h | 19 +++++++++++
clang/lib/CodeGen/CodeGenModule.cpp | 12 +++++++
clang/test/CodeGen/exception-model-flag.c | 38 ++++++++++++++++++++++
3 files changed, 69 insertions(+)
create mode 100644 clang/test/CodeGen/exception-model-flag.c
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 17f367bc02607..ff4ce0b8571ca 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -201,6 +201,25 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// Possible exception handling behavior.
enum class ExceptionHandlingKind { None, SjLj, WinEH, DwarfCFI, Wasm };
+ /// Translate a clang ExceptionHandlingKind into the corresponding LLVM
+ /// ExceptionHandling model.
+ static llvm::ExceptionHandling
+ toExceptionHandling(ExceptionHandlingKind Kind) {
+ switch (Kind) {
+ case ExceptionHandlingKind::None:
+ return llvm::ExceptionHandling::None;
+ case ExceptionHandlingKind::SjLj:
+ return llvm::ExceptionHandling::SjLj;
+ case ExceptionHandlingKind::WinEH:
+ return llvm::ExceptionHandling::WinEH;
+ case ExceptionHandlingKind::DwarfCFI:
+ return llvm::ExceptionHandling::DwarfCFI;
+ case ExceptionHandlingKind::Wasm:
+ return llvm::ExceptionHandling::Wasm;
+ }
+ llvm_unreachable("invalid ExceptionHandlingKind");
+ }
+
enum class SwiftAsyncFramePointerKind {
Auto, // Choose Swift async extended frame info based on deployment target.
Always, // Unconditionally emit Swift async extended frame info.
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index ca110afe9edc8..dec46c6ed3ed3 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1451,6 +1451,18 @@ void CodeGenModule::Release() {
getModule().setLongDoubleFormat(*Format);
}
+ // Record the exception model as a module flag when it differs from the
+ // target default.
+ llvm::ExceptionHandling ExceptionModel =
+ CodeGenOptions::toExceptionHandling(CodeGenOpts.getExceptionHandling());
+ if (ExceptionModel != llvm::ExceptionHandling::None &&
+ ExceptionModel != getTriple().getDefaultExceptionHandling()) {
+ getModule().addModuleFlag(
+ llvm::Module::Error, "exception-model",
+ llvm::MDString::get(getLLVMContext(),
+ llvm::getExceptionModelName(ExceptionModel)));
+ }
+
if (getTriple().isOSzOS()) {
getModule().addModuleFlag(llvm::Module::Warning,
"zos_product_major_version",
diff --git a/clang/test/CodeGen/exception-model-flag.c b/clang/test/CodeGen/exception-model-flag.c
new file mode 100644
index 0000000000000..9a7f93cb00579
--- /dev/null
+++ b/clang/test/CodeGen/exception-model-flag.c
@@ -0,0 +1,38 @@
+// Verify clang records the "exception-model" module flag when the exception
+// model differs from the target triple's default, and omits it otherwise.
+// The cc1 -exception-model option accepts dwarf/sjlj/seh/wasm/none; ARM EHABI
+// is triple-inferred and not user-selectable here.
+
+// i686-linux defaults to DWARF exception handling, so every other model emits.
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=sjlj -emit-llvm %s -o - | FileCheck %s --check-prefix=SJLJ
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=seh -emit-llvm %s -o - | FileCheck %s --check-prefix=WINEH
+
+// SEH maps to the "wineh" spelling regardless of the requesting triple.
+// RUN: %clang_cc1 -triple i686-unknown-windows-gnu -fexceptions -exception-model=seh -emit-llvm %s -o - | FileCheck %s --check-prefix=WINEH
+
+// Wasm EH (needs the backend enable flag) records the "wasm" model.
+// RUN: %clang_cc1 -triple wasm32-unknown-unknown -fexceptions -exception-model=wasm -mllvm -wasm-enable-eh -emit-llvm %s -o - | FileCheck %s --check-prefix=WASM
+
+// DWARF requested on a target that also defaults to DWARF: no flag.
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=dwarf -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+
+// SjLj requested on a target that defaults to SjLj: no flag.
+// RUN: %clang_cc1 -triple armv7-apple-ios -fexceptions -exception-model=sjlj -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+
+// A target that defaults to WinEH records no flag (clang rejects an explicit
+// -exception-model=seh here, so rely on the default).
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fexceptions -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+
+// Explicitly "none": no flag (clang cannot distinguish this from unspecified).
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=none -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+
+// No exception model requested at all: no flag.
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+// RUN: %clang_cc1 -triple armv7-unknown-linux-gnueabi -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+
+void f(void) {}
+
+// SJLJ: !{i32 1, !"exception-model", !"sjlj"}
+// WINEH: !{i32 1, !"exception-model", !"wineh"}
+// WASM: !{i32 1, !"exception-model", !"wasm"}
+// NONE-NOT: "exception-model"
>From 1f58d14d5a04d2b5064db6328b88e9cb7aa45bce Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Tue, 1 Sep 2026 20:43:23 +0200
Subject: [PATCH 2/2] clang: Distinguish unspecified from disabled exception
model
Add ExceptionHandlingKind::Default so clang can tell an unspecified
exception model from an explicit -exception-model=none.
---
clang/include/clang/Basic/CodeGenOptions.def | 2 +-
clang/include/clang/Basic/CodeGenOptions.h | 11 +++++-
.../clang/Basic/DiagnosticFrontendKinds.td | 2 +-
clang/include/clang/Options/Options.td | 2 +-
clang/lib/CodeGen/CGException.cpp | 6 ++--
clang/lib/CodeGen/CodeGenModule.cpp | 9 ++---
clang/lib/Driver/ToolChain.cpp | 2 +-
clang/lib/Driver/ToolChains/Darwin.cpp | 2 +-
clang/lib/Driver/ToolChains/Darwin.h | 2 +-
clang/lib/Driver/ToolChains/NetBSD.cpp | 2 +-
clang/lib/Frontend/CompilerInvocation.cpp | 2 +-
clang/test/CodeGen/exception-model-flag.c | 35 ++++++++++---------
12 files changed, 45 insertions(+), 32 deletions(-)
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 2a04538677005..b96d36ca2ec7f 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -62,7 +62,7 @@ CODEGENOPT(XCOFFReadOnlyPointers, 1, 0, Benign) ///< Set for -mxcoff-roptr.
CODEGENOPT(AllTocData, 1, 0, Benign) ///< AIX -mtocdata
ENUM_CODEGENOPT(FramePointer, FramePointerKind, 3, FramePointerKind::None, Benign) /// frame-pointer: all,non-leaf,non-leaf-no-reserve,reserved,none
-ENUM_CODEGENOPT(ExceptionHandling, ExceptionHandlingKind, 3, ExceptionHandlingKind::None, NotCompatible)
+ENUM_CODEGENOPT(ExceptionHandling, ExceptionHandlingKind, 3, ExceptionHandlingKind::Default, NotCompatible)
CODEGENOPT(ClearASTBeforeBackend , 1, 0, Benign) ///< Free the AST before running backend code generation.
CODEGENOPT(DisableFree , 1, 0, Benign) ///< Don't free memory.
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index ff4ce0b8571ca..17c5dd500d0b0 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -199,13 +199,22 @@ class CodeGenOptions : public CodeGenOptionsBase {
}
/// Possible exception handling behavior.
- enum class ExceptionHandlingKind { None, SjLj, WinEH, DwarfCFI, Wasm };
+ enum class ExceptionHandlingKind {
+ Default,
+ None,
+ SjLj,
+ WinEH,
+ DwarfCFI,
+ Wasm
+ };
/// Translate a clang ExceptionHandlingKind into the corresponding LLVM
/// ExceptionHandling model.
static llvm::ExceptionHandling
toExceptionHandling(ExceptionHandlingKind Kind) {
switch (Kind) {
+ case ExceptionHandlingKind::Default:
+ return llvm::ExceptionHandling::Default;
case ExceptionHandlingKind::None:
return llvm::ExceptionHandling::None;
case ExceptionHandlingKind::SjLj:
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index a10f10502a702..46ccd8f90e57c 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -151,7 +151,7 @@ def err_fe_invalid_multiple_actions : Error<
def err_fe_invalid_alignment : Error<
"invalid value '%1' in '%0'; alignment must be a power of 2">;
def err_fe_invalid_exception_model
- : Error<"invalid exception model '%select{none|sjlj|seh|dwarf|wasm}0' for target '%1'">;
+ : Error<"invalid exception model '%select{default|none|sjlj|seh|dwarf|wasm}0' for target '%1'">;
def err_fe_invalid_source_date_epoch : Error<
"environment variable 'SOURCE_DATE_EPOCH' ('%0') must be a non-negative decimal integer <= %1">;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 37e5c3199a003..ecb2a1affe9a1 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2459,7 +2459,7 @@ def exception_model : Separate<["-"], "exception-model">,
Values<"dwarf,sjlj,seh,wasm,none">,
NormalizedValuesScope<"CodeGenOptions::ExceptionHandlingKind">,
NormalizedValues<["DwarfCFI", "SjLj", "WinEH", "Wasm", "None"]>,
- MarshallingInfoEnum<CodeGenOpts<"ExceptionHandling">, "None">;
+ MarshallingInfoEnum<CodeGenOpts<"ExceptionHandling">, "Default">;
def exception_model_EQ : Joined<["-"], "exception-model=">,
Visibility<[CC1Option]>, Alias<exception_model>;
def fignore_exceptions : Flag<["-"], "fignore-exceptions">, Group<f_Group>,
diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp
index bc09fe767de45..2785c224cf403 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -530,8 +530,10 @@ void CodeGenFunction::EmitStartEHSpec(const Decl *D) {
// throw with types.
// TODO Correctly handle exception specification in Emscripten EH
if (getTarget().getCXXABI() == TargetCXXABI::WebAssembly &&
- CGM.getCodeGenOpts().getExceptionHandling() ==
- CodeGenOptions::ExceptionHandlingKind::None &&
+ (CGM.getCodeGenOpts().getExceptionHandling() ==
+ CodeGenOptions::ExceptionHandlingKind::None ||
+ CGM.getCodeGenOpts().getExceptionHandling() ==
+ CodeGenOptions::ExceptionHandlingKind::Default) &&
EST == EST_Dynamic)
CGM.getDiags().Report(D->getLocation(),
diag::warn_wasm_dynamic_exception_spec_ignored)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index dec46c6ed3ed3..f24ad740ff6c3 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1451,12 +1451,13 @@ void CodeGenModule::Release() {
getModule().setLongDoubleFormat(*Format);
}
- // Record the exception model as a module flag when it differs from the
- // target default.
+ // Record the exception model as a module flag whenever it was specified, so
+ // that the flag's absence unambiguously means unspecified regardless of the
+ // target default. In the absence of a custom module flag merging behavior, an
+ // explicit non-default model could silently merge with a defaulted module.
llvm::ExceptionHandling ExceptionModel =
CodeGenOptions::toExceptionHandling(CodeGenOpts.getExceptionHandling());
- if (ExceptionModel != llvm::ExceptionHandling::None &&
- ExceptionModel != getTriple().getDefaultExceptionHandling()) {
+ if (ExceptionModel != llvm::ExceptionHandling::Default) {
getModule().addModuleFlag(
llvm::Module::Error, "exception-model",
llvm::MDString::get(getLLVMContext(),
diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index 2a17df5ff9132..78bb0f5fcb2a3 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -1460,7 +1460,7 @@ ObjCRuntime ToolChain::getDefaultObjCRuntime(bool isNonFragile) const {
llvm::ExceptionHandling
ToolChain::GetExceptionModel(const llvm::opt::ArgList &Args) const {
- return llvm::ExceptionHandling::None;
+ return llvm::ExceptionHandling::Default;
}
bool ToolChain::isThreadModelSupported(const StringRef Model) const {
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index a0d7d31f1bf15..4100de3ad8fdc 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -3776,7 +3776,7 @@ llvm::ExceptionHandling Darwin::GetExceptionModel(const ArgList &Args) const {
// Darwin uses SjLj exceptions on ARM.
if (getTriple().getArch() != llvm::Triple::arm &&
getTriple().getArch() != llvm::Triple::thumb)
- return llvm::ExceptionHandling::None;
+ return llvm::ExceptionHandling::Default;
// Only watchOS uses the new DWARF/Compact unwinding method.
llvm::Triple Triple(ComputeLLVMTriple(Args));
diff --git a/clang/lib/Driver/ToolChains/Darwin.h b/clang/lib/Driver/ToolChains/Darwin.h
index 1bcc1492b7c3e..3ca5753f3012c 100644
--- a/clang/lib/Driver/ToolChains/Darwin.h
+++ b/clang/lib/Driver/ToolChains/Darwin.h
@@ -290,7 +290,7 @@ class LLVM_LIBRARY_VISIBILITY MachO : public ToolChain {
llvm::ExceptionHandling
GetExceptionModel(const llvm::opt::ArgList &Args) const override {
- return llvm::ExceptionHandling::None;
+ return llvm::ExceptionHandling::Default;
}
virtual StringRef getOSLibraryNameSuffix(bool IgnoreSim = false) const {
diff --git a/clang/lib/Driver/ToolChains/NetBSD.cpp b/clang/lib/Driver/ToolChains/NetBSD.cpp
index f03114b53bb61..65c82329db74d 100644
--- a/clang/lib/Driver/ToolChains/NetBSD.cpp
+++ b/clang/lib/Driver/ToolChains/NetBSD.cpp
@@ -507,7 +507,7 @@ llvm::ExceptionHandling NetBSD::GetExceptionModel(const ArgList &Args) const {
if (TArch == llvm::Triple::arm || TArch == llvm::Triple::armeb ||
TArch == llvm::Triple::thumb || TArch == llvm::Triple::thumbeb)
return llvm::ExceptionHandling::DwarfCFI;
- return llvm::ExceptionHandling::None;
+ return llvm::ExceptionHandling::Default;
}
SanitizerMask
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index ea8368908879a..e516e46cd0aa8 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -572,7 +572,7 @@ static bool FixupInvocation(CompilerInvocation &Invocation,
CodeGenOpts.LargeDataThreshold = TargetOpts.LargeDataThreshold;
if (CodeGenOpts.getExceptionHandling() !=
- CodeGenOptions::ExceptionHandlingKind::None &&
+ CodeGenOptions::ExceptionHandlingKind::Default &&
T.isWindowsMSVCEnvironment())
Diags.Report(diag::err_fe_invalid_exception_model)
<< static_cast<unsigned>(CodeGenOpts.getExceptionHandling()) << T.str();
diff --git a/clang/test/CodeGen/exception-model-flag.c b/clang/test/CodeGen/exception-model-flag.c
index 9a7f93cb00579..2cb74ca78ffa3 100644
--- a/clang/test/CodeGen/exception-model-flag.c
+++ b/clang/test/CodeGen/exception-model-flag.c
@@ -1,9 +1,12 @@
-// Verify clang records the "exception-model" module flag when the exception
-// model differs from the target triple's default, and omits it otherwise.
+// Verify clang records the "exception-model" module flag whenever an exception
+// model is specified on the command line, and omits it only when the model is
+// left unspecified. The flag is emitted even when the requested model matches
+// the target triple's default, so that its absence unambiguously means
+// "unspecified" and conflicting models are rejected at link time.
// The cc1 -exception-model option accepts dwarf/sjlj/seh/wasm/none; ARM EHABI
// is triple-inferred and not user-selectable here.
-// i686-linux defaults to DWARF exception handling, so every other model emits.
+// i686-linux defaults to DWARF exception handling.
// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=sjlj -emit-llvm %s -o - | FileCheck %s --check-prefix=SJLJ
// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=seh -emit-llvm %s -o - | FileCheck %s --check-prefix=WINEH
@@ -13,26 +16,24 @@
// Wasm EH (needs the backend enable flag) records the "wasm" model.
// RUN: %clang_cc1 -triple wasm32-unknown-unknown -fexceptions -exception-model=wasm -mllvm -wasm-enable-eh -emit-llvm %s -o - | FileCheck %s --check-prefix=WASM
-// DWARF requested on a target that also defaults to DWARF: no flag.
-// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=dwarf -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+// A requested model that matches the target default is still recorded, so that
+// the flag's absence always means "unspecified".
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=dwarf -emit-llvm %s -o - | FileCheck %s --check-prefix=DWARF
+// RUN: %clang_cc1 -triple armv7-apple-ios -fexceptions -exception-model=sjlj -emit-llvm %s -o - | FileCheck %s --check-prefix=SJLJ
-// SjLj requested on a target that defaults to SjLj: no flag.
-// RUN: %clang_cc1 -triple armv7-apple-ios -fexceptions -exception-model=sjlj -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
-
-// A target that defaults to WinEH records no flag (clang rejects an explicit
-// -exception-model=seh here, so rely on the default).
-// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fexceptions -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
-
-// Explicitly "none": no flag (clang cannot distinguish this from unspecified).
+// Explicitly "none" disables exceptions and is recorded as such.
// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -fexceptions -exception-model=none -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
-// No exception model requested at all: no flag.
-// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
-// RUN: %clang_cc1 -triple armv7-unknown-linux-gnueabi -emit-llvm %s -o - | FileCheck %s --check-prefix=NONE
+// No exception model requested at all: unspecified, so no flag.
+// RUN: %clang_cc1 -triple i686-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=UNSPEC
+// RUN: %clang_cc1 -triple armv7-unknown-linux-gnueabi -emit-llvm %s -o - | FileCheck %s --check-prefix=UNSPEC
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fexceptions -emit-llvm %s -o - | FileCheck %s --check-prefix=UNSPEC
void f(void) {}
// SJLJ: !{i32 1, !"exception-model", !"sjlj"}
// WINEH: !{i32 1, !"exception-model", !"wineh"}
// WASM: !{i32 1, !"exception-model", !"wasm"}
-// NONE-NOT: "exception-model"
+// DWARF: !{i32 1, !"exception-model", !"dwarf"}
+// NONE: !{i32 1, !"exception-model", !"none"}
+// UNSPEC-NOT: "exception-model"
More information about the llvm-branch-commits
mailing list