[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