[llvm] [Support] Support 5-component VersionTuples (PR #181275)

Adrian Prantl via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 12 17:46:15 PST 2026


https://github.com/adrian-prantl updated https://github.com/llvm/llvm-project/pull/181275

>From 42b5cdeff65d8d51cdfe7dc9bcfb81ab5a123ac1 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Thu, 12 Feb 2026 15:47:52 -0800
Subject: [PATCH] [Support] Support 5-component VersionTuples

LLDB parses compiler versions out of DW_AT_producer DWARF attributes
into a VersionTuple. The Swift compiler recently switched to
5-component version numbers. In order to support this version scheme
without growing the size of VersionTuple, this patch dedicates the
last 10 bits of the build version to a 5th "sub-build" component. The
Swift compiler currently uses 1 digit for this and promises to never
use more than 3 digits for the last 3 components.

This patch still leaves 6 decimal digits for the build component for
other version schemes.

rdar://170181060
---
 llvm/include/llvm/Support/VersionTuple.h    | 63 +++++++++++++++------
 llvm/lib/Support/VersionTuple.cpp           | 31 ++++++++--
 llvm/unittests/Support/VersionTupleTest.cpp | 24 +++++++-
 3 files changed, 93 insertions(+), 25 deletions(-)

diff --git a/llvm/include/llvm/Support/VersionTuple.h b/llvm/include/llvm/Support/VersionTuple.h
index 867f81d74fb4d..009f301e5b7b2 100644
--- a/llvm/include/llvm/Support/VersionTuple.h
+++ b/llvm/include/llvm/Support/VersionTuple.h
@@ -36,36 +36,50 @@ class VersionTuple {
   unsigned Subminor : 31;
   unsigned HasSubminor : 1;
 
-  unsigned Build : 31;
+  unsigned Build : 20;
+  unsigned Subbuild : 10;
   unsigned HasBuild : 1;
+  unsigned HasSubbuild : 1;
 
 public:
   constexpr VersionTuple()
       : Major(0), Minor(0), HasMinor(false), Subminor(0), HasSubminor(false),
-        Build(0), HasBuild(false) {}
+        Build(0), Subbuild(0), HasBuild(false), HasSubbuild(false) {}
 
   explicit constexpr VersionTuple(unsigned Major)
       : Major(Major), Minor(0), HasMinor(false), Subminor(0),
-        HasSubminor(false), Build(0), HasBuild(false) {}
+        HasSubminor(false), Build(0), Subbuild(0), HasBuild(false),
+        HasSubbuild(false) {}
 
   explicit constexpr VersionTuple(unsigned Major, unsigned Minor)
       : Major(Major), Minor(Minor), HasMinor(true), Subminor(0),
-        HasSubminor(false), Build(0), HasBuild(false) {}
+        HasSubminor(false), Build(0), Subbuild(0), HasBuild(false),
+        HasSubbuild(false) {}
 
   explicit constexpr VersionTuple(unsigned Major, unsigned Minor,
                                   unsigned Subminor)
       : Major(Major), Minor(Minor), HasMinor(true), Subminor(Subminor),
-        HasSubminor(true), Build(0), HasBuild(false) {}
+        HasSubminor(true), Build(0), Subbuild(0), HasBuild(false),
+        HasSubbuild(false) {}
 
   explicit constexpr VersionTuple(unsigned Major, unsigned Minor,
                                   unsigned Subminor, unsigned Build)
       : Major(Major), Minor(Minor), HasMinor(true), Subminor(Subminor),
-        HasSubminor(true), Build(Build), HasBuild(true) {}
+        HasSubminor(true), Build(Build), Subbuild(0), HasBuild(true),
+        HasSubbuild(false) {}
+
+  explicit constexpr VersionTuple(unsigned Major, unsigned Minor,
+                                  unsigned Subminor, unsigned Build,
+                                  unsigned Subbuild)
+      : Major(Major), Minor(Minor), HasMinor(true), Subminor(Subminor),
+        HasSubminor(true), Build(Build), Subbuild(Subbuild), HasBuild(true),
+        HasSubbuild(true) {}
 
   /// Determine whether this version information is empty
   /// (e.g., all version components are zero).
   bool empty() const {
-    return Major == 0 && Minor == 0 && Subminor == 0 && Build == 0;
+    return Major == 0 && Minor == 0 && Subminor == 0 && Build == 0 &&
+           Subbuild == 0;
   }
 
   /// Retrieve the major version number.
@@ -92,6 +106,13 @@ class VersionTuple {
     return Build;
   }
 
+  /// Retrieve the subbuild version number, if provided.
+  std::optional<unsigned> getSubbuild() const {
+    if (!HasSubbuild)
+      return std::nullopt;
+    return Subbuild;
+  }
+
   /// Return a version tuple that contains only the first 3 version components.
   VersionTuple withoutBuild() const {
     if (HasBuild)
@@ -106,12 +127,15 @@ class VersionTuple {
   /// Return a version tuple that contains only components that are non-zero.
   VersionTuple normalize() const {
     VersionTuple Result = *this;
-    if (Result.Build == 0) {
-      Result.HasBuild = false;
-      if (Result.Subminor == 0) {
-        Result.HasSubminor = false;
-        if (Result.Minor == 0)
-          Result.HasMinor = false;
+    if (Result.Subbuild == 0) {
+      Result.HasSubbuild = false;
+      if (Result.Build == 0) {
+        Result.HasBuild = false;
+        if (Result.Subminor == 0) {
+          Result.HasSubminor = false;
+          if (Result.Minor == 0)
+            Result.HasMinor = false;
+        }
       }
     }
     return Result;
@@ -121,7 +145,8 @@ class VersionTuple {
   /// provided, minor and subminor version numbers are considered to be zero.
   friend bool operator==(const VersionTuple &X, const VersionTuple &Y) {
     return X.Major == Y.Major && X.Minor == Y.Minor &&
-           X.Subminor == Y.Subminor && X.Build == Y.Build;
+           X.Subminor == Y.Subminor && X.Build == Y.Build &&
+           X.Subbuild == Y.Subbuild;
   }
 
   /// Determine if two version numbers are not equivalent.
@@ -137,8 +162,8 @@ class VersionTuple {
   /// If not provided, minor and subminor version numbers are considered to be
   /// zero.
   friend bool operator<(const VersionTuple &X, const VersionTuple &Y) {
-    return std::tie(X.Major, X.Minor, X.Subminor, X.Build) <
-           std::tie(Y.Major, Y.Minor, Y.Subminor, Y.Build);
+    return std::tie(X.Major, X.Minor, X.Subminor, X.Build, X.Subbuild) <
+           std::tie(Y.Major, Y.Minor, Y.Subminor, Y.Build, Y.Subbuild);
   }
 
   /// Determine whether one version number follows another.
@@ -168,13 +193,13 @@ class VersionTuple {
   }
 
   friend hash_code hash_value(const VersionTuple &VT) {
-    return hash_combine(VT.Major, VT.Minor, VT.Subminor, VT.Build);
+    return hash_combine(VT.Major, VT.Minor, VT.Subminor, VT.Build, VT.Subbuild);
   }
 
   template <typename HasherT, llvm::endianness Endianness>
   friend void addHash(HashBuilder<HasherT, Endianness> &HBuilder,
                       const VersionTuple &VT) {
-    HBuilder.add(VT.Major, VT.Minor, VT.Subminor, VT.Build);
+    HBuilder.add(VT.Major, VT.Minor, VT.Subminor, VT.Build, VT.Subbuild);
   }
 
   /// Retrieve a string representation of the version number.
@@ -203,6 +228,8 @@ template <> struct DenseMapInfo<VersionTuple> {
       Result = detail::combineHashValue(Result, *Subminor);
     if (auto Build = Value.getBuild())
       Result = detail::combineHashValue(Result, *Build);
+    if (auto Subbuild = Value.getSubbuild())
+      Result = detail::combineHashValue(Result, *Subbuild);
 
     return Result;
   }
diff --git a/llvm/lib/Support/VersionTuple.cpp b/llvm/lib/Support/VersionTuple.cpp
index c6e20f1bd3ef4..0a023c08d3039 100644
--- a/llvm/lib/Support/VersionTuple.cpp
+++ b/llvm/lib/Support/VersionTuple.cpp
@@ -35,6 +35,8 @@ raw_ostream &llvm::operator<<(raw_ostream &Out, const VersionTuple &V) {
     Out << '.' << *Subminor;
   if (std::optional<unsigned> Build = V.getBuild())
     Out << '.' << *Build;
+  if (std::optional<unsigned> Subbuild = V.getSubbuild())
+    Out << '.' << *Subbuild;
   return Out;
 }
 
@@ -61,7 +63,7 @@ static bool parseInt(StringRef &input, unsigned &value) {
 }
 
 bool VersionTuple::tryParse(StringRef input) {
-  unsigned major = 0, minor = 0, micro = 0, build = 0;
+  unsigned major = 0, minor = 0, subminor = 0, build = 0, subbuild = 0;
 
   // Parse the major version, [0-9]+
   if (parseInt(input, major))
@@ -84,32 +86,49 @@ bool VersionTuple::tryParse(StringRef input) {
     return false;
   }
 
-  // If we're not done, parse the micro version, \.[0-9]+
+  // If we're not done, parse the subminor version, \.[0-9]+
   if (!input.consume_front("."))
     return true;
-  if (parseInt(input, micro))
+  if (parseInt(input, subminor))
     return true;
 
   if (input.empty()) {
-    *this = VersionTuple(major, minor, micro);
+    *this = VersionTuple(major, minor, subminor);
     return false;
   }
 
-  // If we're not done, parse the micro version, \.[0-9]+
+  // If we're not done, parse the build version, \.[0-9]+
   if (!input.consume_front("."))
     return true;
   if (parseInt(input, build))
     return true;
+  if (build >= 1024 * 1024)
+    return true;
+
+  if (input.empty()) {
+    *this = VersionTuple(major, minor, subminor, build);
+    return false;
+  }
+
+  // And the subbuild version, \.[0-9]+
+  if (!input.consume_front("."))
+    return true;
+  if (parseInt(input, subbuild))
+    return true;
+  if (subbuild >= 1024)
+    return true;
 
   // If we have characters left over, it's an error.
   if (!input.empty())
     return true;
 
-  *this = VersionTuple(major, minor, micro, build);
+  *this = VersionTuple(major, minor, subminor, build, subbuild);
   return false;
 }
 
 VersionTuple VersionTuple::withMajorReplaced(unsigned NewMajor) const {
+  if (HasSubbuild)
+    return VersionTuple(NewMajor, Minor, Subminor, Build, Subbuild);
   if (HasBuild)
     return VersionTuple(NewMajor, Minor, Subminor, Build);
   if (HasSubminor)
diff --git a/llvm/unittests/Support/VersionTupleTest.cpp b/llvm/unittests/Support/VersionTupleTest.cpp
index d498d670fb710..724e365f82360 100644
--- a/llvm/unittests/Support/VersionTupleTest.cpp
+++ b/llvm/unittests/Support/VersionTupleTest.cpp
@@ -17,6 +17,14 @@ TEST(VersionTuple, getAsString) {
   EXPECT_EQ("1.2", VersionTuple(1, 2).getAsString());
   EXPECT_EQ("1.2.3", VersionTuple(1, 2, 3).getAsString());
   EXPECT_EQ("1.2.3.4", VersionTuple(1, 2, 3, 4).getAsString());
+  EXPECT_EQ("1.2.3.4.5", VersionTuple(1, 2, 3, 4, 5).getAsString());
+
+  VersionTuple v(1, 2, 3, 4, 5);
+  EXPECT_EQ(v.getMajor(), 1u);
+  EXPECT_EQ(v.getMinor(), 2u);
+  EXPECT_EQ(v.getSubminor(), 3u);
+  EXPECT_EQ(v.getBuild(), 4u);
+  EXPECT_EQ(v.getSubbuild(), 5u);
 }
 
 TEST(VersionTuple, tryParse) {
@@ -34,18 +42,24 @@ TEST(VersionTuple, tryParse) {
   EXPECT_FALSE(VT.tryParse("1.2.3.4"));
   EXPECT_EQ("1.2.3.4", VT.getAsString());
 
+  EXPECT_FALSE(VT.tryParse("1.2.3.4.5"));
+  EXPECT_EQ("1.2.3.4.5", VT.getAsString());
+
   EXPECT_TRUE(VT.tryParse(""));
   EXPECT_TRUE(VT.tryParse("1."));
   EXPECT_TRUE(VT.tryParse("1.2."));
   EXPECT_TRUE(VT.tryParse("1.2.3."));
   EXPECT_TRUE(VT.tryParse("1.2.3.4."));
-  EXPECT_TRUE(VT.tryParse("1.2.3.4.5"));
+  EXPECT_TRUE(VT.tryParse("1.2.3.4.5."));
+  EXPECT_TRUE(VT.tryParse("1.2.3.4.5.6"));
   EXPECT_TRUE(VT.tryParse("1-2"));
   EXPECT_TRUE(VT.tryParse("1+2"));
   EXPECT_TRUE(VT.tryParse(".1"));
   EXPECT_TRUE(VT.tryParse(" 1"));
   EXPECT_TRUE(VT.tryParse("1 "));
   EXPECT_TRUE(VT.tryParse("."));
+  EXPECT_TRUE(VT.tryParse("1.2.3.1048576"));
+  EXPECT_TRUE(VT.tryParse("1.2.3.4.1024"));
 }
 
 TEST(VersionTuple, withMajorReplaced) {
@@ -76,4 +90,12 @@ TEST(VersionTuple, withMajorReplaced) {
   EXPECT_TRUE(ReplacedVersion.getSubminor().has_value());
   EXPECT_TRUE(ReplacedVersion.getBuild().has_value());
   EXPECT_EQ(VersionTuple(7, 11, 12, 2), ReplacedVersion);
+
+  VT = VersionTuple(101, 11, 12, 2, 8);
+  ReplacedVersion = VT.withMajorReplaced(7);
+  EXPECT_TRUE(ReplacedVersion.getMinor().has_value());
+  EXPECT_TRUE(ReplacedVersion.getSubminor().has_value());
+  EXPECT_TRUE(ReplacedVersion.getBuild().has_value());
+  EXPECT_TRUE(ReplacedVersion.getSubbuild().has_value());
+  EXPECT_EQ(VersionTuple(7, 11, 12, 2, 8), ReplacedVersion);
 }



More information about the llvm-commits mailing list