[llvm] IR: Introduce "thread-model" module flag (PR #222768)

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 13:51:52 PDT 2026


https://github.com/arsenm created https://github.com/llvm/llvm-project/pull/222768

Introduce the replacement for TargetOptions::ThreadModel, but
does not yet wire up the uses. This maintains the current values,
but I question if it makes sense to retain "posix" as the default.
POSIX sounds very target opinionated, but isn't used in a non-binary
way anywhere. Alternatively we could have a simple singlethread binary
flag, or name this something more general.

Follows the precedents of float-abi. It happens the triple default
is just POSIX, but theoretically targets like AVR should be defaulting
to singlethread.

Co-authored-by: Claude (Claude-Opus-4.8) <noreply at anthropic.com>

>From c89c0d6b3b2e34dbc0a6636952e812cef8dfda9c Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Thu, 10 Sep 2026 19:36:54 +0200
Subject: [PATCH] IR: Introduce "thread-model" module flag

Introduce the replacement for TargetOptions::ThreadModel, but
does not yet wire up the uses. This maintains the current values,
but I question if it makes sense to retain "posix" as the default.
POSIX sounds very target opinionated, but isn't used in a non-binary
way anywhere. Alternatively we could have a simple singlethread binary
flag, or name this something more general.

Follows the precedents of float-abi. It happens the triple default
is just POSIX, but theoretically targets like AVR should be defaulting
to singlethread.

Co-authored-by: Claude (Claude-Opus-4.8) <noreply at anthropic.com>
---
 llvm/docs/LangRef.md                          | 29 +++++++++++++++
 llvm/include/llvm/CodeGen/CommandFlags.h      |  2 +-
 llvm/include/llvm/IR/Module.h                 | 12 ++++++
 llvm/include/llvm/Support/CodeGen.h           | 28 ++++++++++++++
 llvm/include/llvm/Target/TargetOptions.h      |  9 +----
 llvm/include/llvm/TargetParser/Triple.h       |  4 ++
 llvm/lib/CodeGen/CommandFlags.cpp             | 11 +++---
 llvm/lib/IR/Module.cpp                        | 11 ++++++
 llvm/lib/IR/Verifier.cpp                      | 11 ++++++
 llvm/lib/TargetParser/Triple.cpp              |  2 +
 .../Assembler/module-flags-thread-model.ll    | 13 +++++++
 llvm/test/Linker/module-flags-thread-model.ll | 29 +++++++++++++++
 .../Verifier/module-flags-thread-model.ll     | 37 +++++++++++++++++++
 13 files changed, 184 insertions(+), 14 deletions(-)
 create mode 100644 llvm/test/Assembler/module-flags-thread-model.ll
 create mode 100644 llvm/test/Linker/module-flags-thread-model.ll
 create mode 100644 llvm/test/Verifier/module-flags-thread-model.ll

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 14caff88243c1..89b0c1ec9f476 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9489,6 +9489,35 @@ conflicting floating-point ABIs is rejected. For example:
 !0 = !{i32 1, !"float-abi", !"hard"}
 ```
 
+### Thread Model Module Flags Metadata
+
+This module flag describes the threading model that the module was
+compiled for, which may influence how atomic operations are
+lowered. The value is a string and must be one of:
+
+```{list-table}
+:header-rows: 1
+:widths: 30 70
+* - Value
+  - Meaning
+
+* - `"posix"`
+  - The POSIX threading model: the module may run in a multi-threaded
+    environment.
+
+* - `"single"`
+  - The single-threaded model: the module runs in a known single-threaded
+    environment, so atomic operations may be lowered to their non-atomic
+    equivalents.
+```
+
+When the flag is absent, the target's default thread model is used. The flag
+must use the `error` merge behavior. For example:
+```
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"single"}
+```
+
 ### Target ABI Module Flags Metadata
 
 This module flag names the target ABI that the module was compiled
diff --git a/llvm/include/llvm/CodeGen/CommandFlags.h b/llvm/include/llvm/CodeGen/CommandFlags.h
index a13e1592e2a0d..841cb22808d05 100644
--- a/llvm/include/llvm/CodeGen/CommandFlags.h
+++ b/llvm/include/llvm/CodeGen/CommandFlags.h
@@ -44,7 +44,7 @@ LLVM_ABI std::vector<std::string> getMAttrs();
 LLVM_ABI Reloc::Model getRelocModel();
 LLVM_ABI std::optional<Reloc::Model> getExplicitRelocModel();
 
-LLVM_ABI ThreadModel::Model getThreadModel();
+LLVM_ABI ThreadModel getThreadModel();
 
 LLVM_ABI CodeModel::Model getCodeModel();
 LLVM_ABI std::optional<CodeModel::Model> getExplicitCodeModel();
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 2fb7eff6daa2a..017394b0018b3 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -1085,6 +1085,18 @@ class LLVM_ABI Module {
   FloatABI::ABIType getFloatABI() const;
   /// @}
 
+  /// @}
+  /// @name Utility function for querying and setting the thread model
+  /// @{
+
+  /// Returns the thread model recorded by the "thread-model" module flag, or
+  /// the model implied by the target triple when the flag is absent.
+  ThreadModel getThreadModel() const;
+
+  /// Set the thread model.
+  void setThreadModel(ThreadModel Model);
+  /// @}
+
   /// @}
   /// @name Utility function for querying and setting the large data threshold
   /// @{
diff --git a/llvm/include/llvm/Support/CodeGen.h b/llvm/include/llvm/Support/CodeGen.h
index e61a36ed42d96..22b168597be99 100644
--- a/llvm/include/llvm/Support/CodeGen.h
+++ b/llvm/include/llvm/Support/CodeGen.h
@@ -137,6 +137,34 @@ namespace llvm {
   }
   } // namespace FloatABI
 
+  /// The threading model to assume for lowering, e.g. of atomics.
+  enum class ThreadModel {
+    POSIX,  // POSIX Threads
+    Single, // Single Threaded Environment
+  };
+
+  /// Parse the string spelling used by the "thread-model" IR module flag into a
+  /// ThreadModel.
+  inline std::optional<ThreadModel> parseThreadModel(StringRef S) {
+    if (S == "posix")
+      return ThreadModel::POSIX;
+    if (S == "single")
+      return ThreadModel::Single;
+    return std::nullopt;
+  }
+
+  /// Returns the string spelling used by the "thread-model" IR module flag for
+  /// a ThreadModel.
+  inline StringRef getThreadModelName(ThreadModel TM) {
+    switch (TM) {
+    case ThreadModel::POSIX:
+      return "posix";
+    case ThreadModel::Single:
+      return "single";
+    }
+    return "";
+  }
+
   enum class EABI {
     Unknown,
     Default, // Default means not specified
diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h
index e94e43f0e867f..2bf9877e318f9 100644
--- a/llvm/include/llvm/Target/TargetOptions.h
+++ b/llvm/include/llvm/Target/TargetOptions.h
@@ -38,13 +38,6 @@ enum JumpTableType {
 };
 }
 
-namespace ThreadModel {
-enum Model {
-  POSIX, // POSIX Threads
-  Single // Single Threaded Environment
-};
-}
-
 enum class BasicBlockSection {
   All,    // Use Basic Block Sections for all basic blocks.  A section
           // for every basic block can significantly bloat object file sizes.
@@ -315,7 +308,7 @@ class TargetOptions {
 
   /// ThreadModel - This flag specifies the type of threading model to assume
   /// for things like atomics
-  ThreadModel::Model ThreadModel = ThreadModel::POSIX;
+  llvm::ThreadModel ThreadModel = llvm::ThreadModel::POSIX;
 
   /// EABIVersion - This flag specifies the EABI version
   EABI EABIVersion = EABI::Default;
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index fd6483fd120df..b023f0f12a69b 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -1258,6 +1258,10 @@ class Triple {
   /// Tests if the target's default floating-point ABI is hard float.
   bool isHardFloatABI() const { return getDefaultFloatABI() == FloatABI::Hard; }
 
+  /// Returns the default threading model for this target triple, i.e. the model
+  /// used when the "thread-model" module flag is absent.
+  LLVM_ABI ThreadModel getDefaultThreadModel() const;
+
   /// Returns the default floating-point format for the "long double" type. A
   /// particular module may override this default.
   LLVM_ABI LongDoubleFormat getDefaultLongDoubleFormat() const;
diff --git a/llvm/lib/CodeGen/CommandFlags.cpp b/llvm/lib/CodeGen/CommandFlags.cpp
index 2025979ce9178..8410c10843bd4 100644
--- a/llvm/lib/CodeGen/CommandFlags.cpp
+++ b/llvm/lib/CodeGen/CommandFlags.cpp
@@ -70,7 +70,7 @@ CGOPT(std::string, MCPU)
 CGOPT(std::string, MTune)
 CGLIST(std::string, MAttrs)
 CGOPT_EXP(Reloc::Model, RelocModel)
-CGOPT(ThreadModel::Model, ThreadModel)
+CGOPT(ThreadModel, ThreadModel)
 CGOPT_EXP(CodeModel::Model, CodeModel)
 CGOPT_EXP(uint64_t, LargeDataThreshold)
 CGOPT(ExceptionHandling, ExceptionModel)
@@ -157,12 +157,13 @@ codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
                      "Combination of ropi and rwpi")));
   CGBINDOPT(RelocModel);
 
-  static cl::opt<ThreadModel::Model> ThreadModel(
+  static cl::opt<ThreadModel> ThreadModel(
       "thread-model", cl::desc("Choose threading model"),
-      cl::init(ThreadModel::POSIX),
+      cl::init(llvm::ThreadModel::POSIX),
       cl::values(
-          clEnumValN(ThreadModel::POSIX, "posix", "POSIX thread model"),
-          clEnumValN(ThreadModel::Single, "single", "Single thread model")));
+          clEnumValN(llvm::ThreadModel::POSIX, "posix", "POSIX thread model"),
+          clEnumValN(llvm::ThreadModel::Single, "single",
+                     "Single thread model")));
   CGBINDOPT(ThreadModel);
 
   static cl::opt<CodeModel::Model> CodeModel(
diff --git a/llvm/lib/IR/Module.cpp b/llvm/lib/IR/Module.cpp
index 510d33c0ad295..a1b2c2fac1aed 100644
--- a/llvm/lib/IR/Module.cpp
+++ b/llvm/lib/IR/Module.cpp
@@ -706,6 +706,17 @@ FloatABI::ABIType Module::getFloatABI() const {
   return getTargetTriple().getDefaultFloatABI();
 }
 
+ThreadModel Module::getThreadModel() const {
+  if (auto *Val = cast_or_null<MDString>(getModuleFlag("thread-model")))
+    return *parseThreadModel(Val->getString());
+  return getTargetTriple().getDefaultThreadModel();
+}
+
+void Module::setThreadModel(ThreadModel Model) {
+  addModuleFlag(ModFlagBehavior::Error, "thread-model",
+                MDString::get(getContext(), getThreadModelName(Model)));
+}
+
 std::optional<uint64_t> Module::getLargeDataThreshold() const {
   auto *Val =
       cast_or_null<ConstantAsMetadata>(getModuleFlag("Large Data Threshold"));
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 12ab6e9c34a5e..0b1eac7f7818d 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2112,6 +2112,17 @@ Verifier::visitModuleFlag(const MDNode *Op,
     return;
   }
 
+  if (Name == "thread-model") {
+    Check(MFB == Module::Error,
+          "thread-model module flag must use 'error' merge behavior", Op);
+    const MDString *Value = dyn_cast_or_null<MDString>(Op->getOperand(2));
+    Check(Value, "thread-model metadata requires a string argument");
+    if (Value)
+      Check(parseThreadModel(Value->getString()).has_value(),
+            "invalid thread-model metadata value", Op);
+    return;
+  }
+
   if (Name == "target-abi") {
     const MDString *Value = dyn_cast_or_null<MDString>(Op->getOperand(2));
     Check(Value && !Value->getString().empty(),
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
index 7d67961256f92..7bf669ce8b84a 100644
--- a/llvm/lib/TargetParser/Triple.cpp
+++ b/llvm/lib/TargetParser/Triple.cpp
@@ -2538,6 +2538,8 @@ FloatABI::ABIType Triple::getDefaultFloatABI() const {
   return FloatABI::Hard;
 }
 
+ThreadModel Triple::getDefaultThreadModel() const { return ThreadModel::POSIX; }
+
 LongDoubleFormat Triple::getDefaultLongDoubleFormat() const {
   switch (getArch()) {
   case loongarch64:
diff --git a/llvm/test/Assembler/module-flags-thread-model.ll b/llvm/test/Assembler/module-flags-thread-model.ll
new file mode 100644
index 0000000000000..cd7e8a9abb75c
--- /dev/null
+++ b/llvm/test/Assembler/module-flags-thread-model.ll
@@ -0,0 +1,13 @@
+; RUN: split-file %s %t
+; RUN: llvm-as < %t/posix.ll | llvm-dis | FileCheck %s --check-prefix=POSIX
+; RUN: llvm-as < %t/single.ll | llvm-dis | FileCheck %s --check-prefix=SINGLE
+
+;--- posix.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"posix"}
+; POSIX: !0 = !{i32 1, !"thread-model", !"posix"}
+
+;--- single.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"single"}
+; SINGLE: !0 = !{i32 1, !"thread-model", !"single"}
diff --git a/llvm/test/Linker/module-flags-thread-model.ll b/llvm/test/Linker/module-flags-thread-model.ll
new file mode 100644
index 0000000000000..e300131573fd1
--- /dev/null
+++ b/llvm/test/Linker/module-flags-thread-model.ll
@@ -0,0 +1,29 @@
+; RUN: split-file %s %t
+
+;; A module without the flag links cleanly with one that sets it, and
+;; the explicit value is preserved.
+
+; RUN: llvm-link %t/none.ll %t/single.ll -S -o - | FileCheck %s --check-prefix=SINGLE
+; RUN: llvm-link %t/single.ll %t/none.ll -S -o - | FileCheck %s --check-prefix=SINGLE
+
+;; Two modules that agree link cleanly.
+; RUN: llvm-link %t/single.ll %t/single.ll -S -o - | FileCheck %s --check-prefix=SINGLE
+
+;; Two modules that disagree are rejected by the 'error' merge behavior.
+; RUN: not llvm-link %t/single.ll %t/posix.ll -S -o /dev/null 2>&1 | FileCheck %s --check-prefix=CONFLICT
+
+; SINGLE: !{i32 1, !"thread-model", !"single"}
+; CONFLICT: linking module flags 'thread-model': IDs have conflicting values
+
+;--- none.ll
+define void @f() {
+  ret void
+}
+
+;--- single.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"single"}
+
+;--- posix.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"posix"}
diff --git a/llvm/test/Verifier/module-flags-thread-model.ll b/llvm/test/Verifier/module-flags-thread-model.ll
new file mode 100644
index 0000000000000..7c05e71bbb352
--- /dev/null
+++ b/llvm/test/Verifier/module-flags-thread-model.ll
@@ -0,0 +1,37 @@
+; RUN: split-file %s %t
+; RUN: not llvm-as < %t/not-string.ll -disable-output 2>&1 | FileCheck %s --check-prefix=NOTSTRING
+; RUN: not llvm-as < %t/bad-value.ll -disable-output 2>&1 | FileCheck %s --check-prefix=BADVALUE
+; RUN: not llvm-as < %t/empty-value.ll -disable-output 2>&1 | FileCheck %s --check-prefix=EMPTY
+; RUN: not llvm-as < %t/too-few.ll -disable-output 2>&1 | FileCheck %s --check-prefix=TOOFEW
+; RUN: not llvm-as < %t/too-many.ll -disable-output 2>&1 | FileCheck %s --check-prefix=TOOMANY
+; RUN: not llvm-as < %t/bad-behavior.ll -disable-output 2>&1 | FileCheck %s --check-prefix=BEHAVIOR
+
+;--- not-string.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", i32 1}
+; NOTSTRING: thread-model metadata requires a string argument
+
+;--- bad-value.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"multi"}
+; BADVALUE: invalid thread-model metadata value
+
+;--- empty-value.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !""}
+; EMPTY: invalid thread-model metadata value
+
+;--- too-few.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model"}
+; TOOFEW: incorrect number of operands in module flag
+
+;--- too-many.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"thread-model", !"single", !"extra"}
+; TOOMANY: incorrect number of operands in module flag
+
+;--- bad-behavior.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 2, !"thread-model", !"single"}
+; BEHAVIOR: thread-model module flag must use 'error' merge behavior



More information about the llvm-commits mailing list