[llvm] IR: Introduce "float-abi" module flag (PR #210821)
Matt Arsenault via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 07:34:46 PDT 2026
https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/210821
>From e8e62788ce457ae0838dad2e4e991f13c0731be0 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Mon, 20 Jul 2026 12:52:33 +0200
Subject: [PATCH] IR: Introduce "float-abi" module flag
This is intended to eliminate the FloatABIType TargetOptions field.
Co-authored-by: Claude (Claude-Opus-4.8) <noreply at anthropic.com>
---
llvm/docs/LangRef.md | 31 ++++++++++++++
llvm/include/llvm/IR/Module.h | 9 ++++
llvm/include/llvm/Support/CodeGen.h | 41 +++++++++++++++----
llvm/lib/IR/AutoUpgrade.cpp | 28 +++++++------
llvm/lib/IR/Module.cpp | 6 +++
llvm/lib/IR/Verifier.cpp | 10 +++++
llvm/test/Assembler/module-flags-float-abi.ll | 13 ++++++
llvm/test/Bitcode/upgrade-ppc-float-abi.ll | 16 ++++++++
llvm/test/Linker/module-flags-float-abi.ll | 28 +++++++++++++
llvm/test/Verifier/module-flags-float-abi.ll | 37 +++++++++++++++++
10 files changed, 199 insertions(+), 20 deletions(-)
create mode 100644 llvm/test/Assembler/module-flags-float-abi.ll
create mode 100644 llvm/test/Linker/module-flags-float-abi.ll
create mode 100644 llvm/test/Verifier/module-flags-float-abi.ll
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index a1c852e52c848..8c6cf2b6236ce 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9407,6 +9407,37 @@ enum is the smallest type which can represent all of its values:
!1 = !{i32 1, !"short_enum", i32 0}
```
+### Float ABI Module Flags Metadata
+
+This module flag describes the floating-point ABI (the calling convention used
+to pass and return floating-point values) that the module was compiled for. The
+value is an `MDString` and must be one of:
+
+```{list-table}
+:header-rows: 1
+:widths: 30 70
+* - Value
+ - Meaning
+
+* - `"soft"`
+ - The software floating-point calling convention is used: floating-point
+ values are passed in general-purpose (integer) registers. Note this is
+ independent of whether floating-point hardware is used to perform
+ operations; see the `use-soft-float` function attribute for that.
+
+* - `"hard"`
+ - The hardware floating-point calling convention is used: floating-point
+ values are passed in floating-point registers.
+```
+
+When the flag is absent, the target's default floating-point ABI is used. The
+flag must use the `error` merge behavior, so that linking modules with
+conflicting floating-point ABIs is rejected. For example:
+```
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"hard"}
+```
+
### Long Double Type Module Flags Metadata
Describe the floating-point format used by libm for `long double`. The
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index cfc6b34ced5ce..16dad6ad446fb 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -1049,6 +1049,15 @@ class LLVM_ABI Module {
void setCodeModel(CodeModel::Model CL);
/// @}
+ /// @}
+ /// @name Utility function for querying the floating-point ABI
+ /// @{
+
+ /// Returns the floating-point ABI recorded by the "float-abi" module flag, or
+ /// FloatABI::Default when the flag is absent (meaning the target default).
+ FloatABI::ABIType getFloatABI() const;
+ /// @}
+
/// @}
/// @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 d29b24101c723..5bc3993a02dff 100644
--- a/llvm/include/llvm/Support/CodeGen.h
+++ b/llvm/include/llvm/Support/CodeGen.h
@@ -14,6 +14,7 @@
#ifndef LLVM_SUPPORT_CODEGEN_H
#define LLVM_SUPPORT_CODEGEN_H
+#include "llvm/ADT/StringRef.h"
#include <cstdint>
#include <optional>
@@ -62,14 +63,6 @@ namespace llvm {
///< PPA1 is used instead of an .eh_frame section.
};
- namespace FloatABI {
- enum ABIType {
- Default, // Target-specific (either soft or hard depending on triple, etc).
- Soft, // Soft float.
- Hard // Hard float.
- };
- }
-
/// The floating-point format used for the target's "long double" type.
enum class LongDoubleFormat {
IEEEsingle,
@@ -79,6 +72,38 @@ namespace llvm {
PPCDoubleDouble,
};
+ namespace FloatABI {
+ enum ABIType {
+ Default, // Target-specific (either soft or hard depending on triple, etc).
+ Soft, // Soft float.
+ Hard // Hard float.
+ };
+
+ /// Parse the string spelling used by the "float-abi" IR module flag into an
+ /// ABIType.
+ inline std::optional<ABIType> parseABIType(StringRef S) {
+ if (S == "soft")
+ return Soft;
+ if (S == "hard")
+ return Hard;
+ return std::nullopt;
+ }
+
+ /// Returns the string spelling used by the "float-abi" IR module flag for a
+ /// Soft or Hard ABIType. Default has no spelling.
+ inline StringRef getABITypeName(ABIType ABI) {
+ switch (ABI) {
+ case Soft:
+ return "soft";
+ case Hard:
+ return "hard";
+ case Default:
+ break;
+ }
+ return "";
+ }
+ } // namespace FloatABI
+
enum class EABI {
Unknown,
Default, // Default means not specified
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index df79a934bd290..297bdee30760c 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -6629,22 +6629,26 @@ bool llvm::UpgradeModuleFlags(Module &M) {
// clang/PowerPC used to use "float-abi" to describe the long double format;
// it has been renamed to "long-double-type", with its values changed to the
- // corresponding IR floating-point type names. Map any unrecognized value
- // (which was never valid) to the PowerPC default ppc_fp128 rather than
- // erasing the flag.
+ // corresponding IR floating-point type names.
if (M.getTargetTriple().isPPC() && ID->getString() == "float-abi") {
StringRef Format;
if (auto *S = dyn_cast_or_null<MDString>(Op->getOperand(2)))
Format = S->getString();
- StringRef NewType = StringSwitch<StringRef>(Format)
- .Case("ieeequad", "fp128")
- .Case("ieeedouble", "double")
- .Default("ppc_fp128");
- Metadata *Ops[3] = {Op->getOperand(0),
- MDString::get(M.getContext(), "long-double-type"),
- MDString::get(M.getContext(), NewType)};
- ModFlags->setOperand(I, MDNode::get(M.getContext(), Ops));
- Changed = true;
+
+ // The "float-abi" key is now reserved for the target-independent
+ // soft/hard ABI flag, so leave a valid value alone. Map any other value
+ // (including unrecognized ones, which were never valid) to the default.
+ if (!FloatABI::parseABIType(Format)) {
+ StringRef NewType = StringSwitch<StringRef>(Format)
+ .Case("ieeequad", "fp128")
+ .Case("ieeedouble", "double")
+ .Default("ppc_fp128");
+ Metadata *Ops[3] = {Op->getOperand(0),
+ MDString::get(M.getContext(), "long-double-type"),
+ MDString::get(M.getContext(), NewType)};
+ ModFlags->setOperand(I, MDNode::get(M.getContext(), Ops));
+ Changed = true;
+ }
}
}
diff --git a/llvm/lib/IR/Module.cpp b/llvm/lib/IR/Module.cpp
index a4a32613e6312..3dcc9ec5dac3f 100644
--- a/llvm/lib/IR/Module.cpp
+++ b/llvm/lib/IR/Module.cpp
@@ -682,6 +682,12 @@ void Module::setCodeModel(CodeModel::Model CL) {
addModuleFlag(ModFlagBehavior::Error, "Code Model", CL);
}
+FloatABI::ABIType Module::getFloatABI() const {
+ if (auto *Val = dyn_cast_or_null<MDString>(getModuleFlag("float-abi")))
+ return FloatABI::parseABIType(Val->getString()).value_or(FloatABI::Default);
+ return FloatABI::Default;
+}
+
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 90ad2e5bc8f68..50e7bc5edefea 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2013,6 +2013,16 @@ Verifier::visitModuleFlag(const MDNode *Op,
"invalid long-double-type metadata value", Op);
}
+ if (ID->getString() == "float-abi") {
+ Check(MFB == Module::Error,
+ "float-abi module flag must use 'error' merge behavior", Op);
+ const MDString *Value = dyn_cast_or_null<MDString>(Op->getOperand(2));
+ Check(Value, "float-abi metadata requires a string argument");
+ if (Value)
+ Check(FloatABI::parseABIType(Value->getString()).has_value(),
+ "invalid float-abi metadata value", Op);
+ }
+
if (ID->getString() == "Linker Options") {
// If the llvm.linker.options named metadata exists, we assume that the
// bitcode reader has upgraded the module flag. Otherwise the flag might
diff --git a/llvm/test/Assembler/module-flags-float-abi.ll b/llvm/test/Assembler/module-flags-float-abi.ll
new file mode 100644
index 0000000000000..7aff95e950105
--- /dev/null
+++ b/llvm/test/Assembler/module-flags-float-abi.ll
@@ -0,0 +1,13 @@
+; RUN: split-file %s %t
+; RUN: llvm-as < %t/soft.ll | llvm-dis | FileCheck %s --check-prefix=SOFT
+; RUN: llvm-as < %t/hard.ll | llvm-dis | FileCheck %s --check-prefix=HARD
+
+;--- soft.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"soft"}
+; SOFT: !0 = !{i32 1, !"float-abi", !"soft"}
+
+;--- hard.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"hard"}
+; HARD: !0 = !{i32 1, !"float-abi", !"hard"}
diff --git a/llvm/test/Bitcode/upgrade-ppc-float-abi.ll b/llvm/test/Bitcode/upgrade-ppc-float-abi.ll
index 31df56d973723..592ee6990453a 100644
--- a/llvm/test/Bitcode/upgrade-ppc-float-abi.ll
+++ b/llvm/test/Bitcode/upgrade-ppc-float-abi.ll
@@ -8,6 +8,8 @@
; RUN: llvm-as < %t/ieeedouble.ll | llvm-dis | FileCheck %s --check-prefix=IEEEDOUBLE
; RUN: llvm-as < %t/unrecognized.ll | llvm-dis | FileCheck %s --check-prefix=UNRECOGNIZED
; RUN: llvm-as < %t/arm.ll | llvm-dis | FileCheck %s --check-prefix=ARM
+; RUN: llvm-as < %t/ppc-hard.ll | llvm-dis | FileCheck %s --check-prefix=HARD
+; RUN: llvm-as < %t/ppc-soft.ll | llvm-dis | FileCheck %s --check-prefix=SOFT
;; All the old PowerPC long double format spellings are upgraded to the new key
;; and IR type-name values.
@@ -43,3 +45,17 @@ target triple = "armv7-unknown-linux-gnueabihf"
!llvm.module.flags = !{!0}
!0 = !{i32 1, !"float-abi", !"hard"}
; ARM: !0 = !{i32 1, !"float-abi", !"hard"}
+
+;; The "float-abi" key now also names the target-independent soft/hard ABI flag;
+;; those values are valid and must be left untouched, even on PowerPC.
+;--- ppc-hard.ll
+target triple = "powerpc64le-unknown-linux-gnu"
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"hard"}
+; HARD: !0 = !{i32 1, !"float-abi", !"hard"}
+
+;--- ppc-soft.ll
+target triple = "powerpc64le-unknown-linux-gnu"
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"soft"}
+; SOFT: !0 = !{i32 1, !"float-abi", !"soft"}
diff --git a/llvm/test/Linker/module-flags-float-abi.ll b/llvm/test/Linker/module-flags-float-abi.ll
new file mode 100644
index 0000000000000..a53175373a985
--- /dev/null
+++ b/llvm/test/Linker/module-flags-float-abi.ll
@@ -0,0 +1,28 @@
+; RUN: split-file %s %t
+
+;; A module without the flag (target default) links cleanly with one that sets
+;; it, and the explicit value is preserved.
+; RUN: llvm-link %t/none.ll %t/hard.ll -S -o - | FileCheck %s --check-prefix=HARD
+; RUN: llvm-link %t/hard.ll %t/none.ll -S -o - | FileCheck %s --check-prefix=HARD
+
+;; Two modules that agree link cleanly.
+; RUN: llvm-link %t/hard.ll %t/hard.ll -S -o - | FileCheck %s --check-prefix=HARD
+
+;; Two modules that disagree are rejected by the 'error' merge behavior.
+; RUN: not llvm-link %t/hard.ll %t/soft.ll -S -o /dev/null 2>&1 | FileCheck %s --check-prefix=CONFLICT
+
+; HARD: !{i32 1, !"float-abi", !"hard"}
+; CONFLICT: linking module flags 'float-abi': IDs have conflicting values
+
+;--- none.ll
+define void @f() {
+ ret void
+}
+
+;--- hard.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"hard"}
+
+;--- soft.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"soft"}
diff --git a/llvm/test/Verifier/module-flags-float-abi.ll b/llvm/test/Verifier/module-flags-float-abi.ll
new file mode 100644
index 0000000000000..73cff95015052
--- /dev/null
+++ b/llvm/test/Verifier/module-flags-float-abi.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, !"float-abi", i32 1}
+; NOTSTRING: float-abi metadata requires a string argument
+
+;--- bad-value.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"softfp"}
+; BADVALUE: invalid float-abi metadata value
+
+;--- empty-value.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !""}
+; EMPTY: invalid float-abi metadata value
+
+;--- too-few.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi"}
+; TOOFEW: incorrect number of operands in module flag
+
+;--- too-many.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"float-abi", !"hard", !"extra"}
+; TOOMANY: incorrect number of operands in module flag
+
+;--- bad-behavior.ll
+!llvm.module.flags = !{!0}
+!0 = !{i32 2, !"float-abi", !"hard"}
+; BEHAVIOR: float-abi module flag must use 'error' merge behavior
More information about the llvm-commits
mailing list