[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