Author: Adrian Prantl Date: 2026-02-13T12:45:04-08:00 New Revision: 43fcc262e7504f1d4219435c38e4c251199cbeb7
URL: https://github.com/llvm/llvm-project/commit/43fcc262e7504f1d4219435c38e4c251199cbeb7 DIFF: https://github.com/llvm/llvm-project/commit/43fcc262e7504f1d4219435c38e4c251199cbeb7.diff LOG: [Support] Support 5-component VersionTuples (#181275) 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 Added: Modified: clang/lib/Driver/ToolChains/Darwin.cpp lld/MachO/Driver.cpp lld/test/MachO/platform-version.s llvm/include/llvm/Support/VersionTuple.h llvm/lib/Support/VersionTuple.cpp llvm/unittests/Support/VersionTupleTest.cpp Removed: ################################################################################ diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp index 073f23950160c..1c95a79a52a9c 100644 --- a/clang/lib/Driver/ToolChains/Darwin.cpp +++ b/clang/lib/Driver/ToolChains/Darwin.cpp @@ -1119,10 +1119,14 @@ VersionTuple MachO::getLinkerVersion(const llvm::opt::ArgList &Args) const { } VersionTuple NewLinkerVersion; - if (Arg *A = Args.getLastArg(options::OPT_mlinker_version_EQ)) - if (NewLinkerVersion.tryParse(A->getValue())) + if (Arg *A = Args.getLastArg(options::OPT_mlinker_version_EQ)) { + // Rejecting subbuild version is probably not necessary, but some + // existing tests depend on this. + if (NewLinkerVersion.tryParse(A->getValue()) || + NewLinkerVersion.getSubbuild()) getDriver().Diag(diag::err_drv_invalid_version_number) - << A->getAsString(Args); + << A->getAsString(Args); + } LinkerVersion = NewLinkerVersion; return *LinkerVersion; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 973b3f5535cb4..58fbe64c2d1f9 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -874,13 +874,12 @@ static PlatformVersion parsePlatformVersion(const Arg *arg) { .Default(PLATFORM_UNKNOWN); if (platformVersion.platform == PLATFORM_UNKNOWN) error(Twine("malformed platform: ") + platformStr); - // TODO: check validity of version strings, which varies by platform - // NOTE: ld64 accepts version strings with 5 components - // llvm::VersionTuple accepts no more than 4 components - // Has Apple ever published version strings with 5 components? - if (platformVersion.minimum.tryParse(minVersionStr)) + // The underlying load command only supports 3 components. + if (platformVersion.minimum.tryParse(minVersionStr) || + platformVersion.minimum.getBuild()) error(Twine("malformed minimum version: ") + minVersionStr); - if (platformVersion.sdk.tryParse(sdkVersionStr)) + if (platformVersion.sdk.tryParse(sdkVersionStr) || + platformVersion.sdk.getBuild()) error(Twine("malformed sdk version: ") + sdkVersionStr); return platformVersion; } diff --git a/lld/test/MachO/platform-version.s b/lld/test/MachO/platform-version.s index 57fbae62b2ffc..46957ccd44492 100644 --- a/lld/test/MachO/platform-version.s +++ b/lld/test/MachO/platform-version.s @@ -25,10 +25,10 @@ # RUN: -platform_version iOS 1 2.a \ # RUN: | FileCheck --check-prefix=FAIL-MALFORM %s # RUN: not %no-arg-lld -arch x86_64 -o %t %t.o 2>&1 \ -# RUN: -platform_version tvOS 1.2.3.4.5 10 \ +# RUN: -platform_version tvOS 1.2.3.4 10 \ # RUN: | FileCheck --check-prefix=FAIL-MALFORM %s # RUN: not %no-arg-lld -arch x86_64 -o %t %t.o 2>&1 \ -# RUN: -platform_version watchOS 10 1.2.3.4.5 \ +# RUN: -platform_version watchOS 10 1.2.3.4 \ # RUN: | FileCheck --check-prefix=FAIL-MALFORM %s # FAIL-MALFORM-NOT: malformed platform: {{.*}} # FAIL-MALFORM: malformed {{minimum|sdk}} version: {{.*}} @@ -40,7 +40,7 @@ # RUN: %no-arg-lld -arch x86_64 -o %t %t.o 2>&1 \ # RUN: -platform_version "iOS Simulator" 1.2.3 5.6.7 # RUN: %no-arg-lld -arch x86_64 -o %t %t.o 2>&1 \ -# RUN: -platform_version tvOS-Simulator 1.2.3.4 5.6.7.8 +# RUN: -platform_version tvOS-Simulator 1.2.3 5.6.7 # RUN: %no-arg-lld -arch x86_64 -o %t %t.o 2>&1 \ # RUN: -platform_version watchOS-Simulator 1 5 # RUN: %no-arg-lld -arch x86_64 -o %t %t.o 2>&1 \ diff --git a/llvm/include/llvm/Support/VersionTuple.h b/llvm/include/llvm/Support/VersionTuple.h index 867f81d74fb4d..e4500a714d12b 100644 --- a/llvm/include/llvm/Support/VersionTuple.h +++ b/llvm/include/llvm/Support/VersionTuple.h @@ -36,37 +36,52 @@ 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) {} + + std::tuple<unsigned, unsigned, unsigned, unsigned, unsigned> asTuple() const { + return {Major, Minor, Subminor, Build, Subbuild}; + } /// 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; - } + bool empty() const { return *this == VersionTuple(); } /// Retrieve the major version number. unsigned getMajor() const { return Major; } @@ -92,6 +107,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 +128,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; @@ -120,8 +145,7 @@ class VersionTuple { /// Determine if two version numbers are equivalent. If not /// 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; + return X.asTuple() == Y.asTuple(); } /// Determine if two version numbers are not equivalent. @@ -137,8 +161,7 @@ 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 X.asTuple() < Y.asTuple(); } /// Determine whether one version number follows another. @@ -168,13 +191,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 +226,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); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
