[clang][darwin] Add support for macOS -> Mac Catalyst
authorAlex Lorenz <arphaman@gmail.com>
Wed, 14 Jul 2021 04:49:56 +0000 (21:49 -0700)
committerAlex Lorenz <arphaman@gmail.com>
Tue, 20 Jul 2021 21:25:33 +0000 (14:25 -0700)
version remapping to the Darwin SDK Info

Differential Revision: https://reviews.llvm.org/D105958

clang/include/clang/Basic/DarwinSDKInfo.h
clang/lib/Basic/DarwinSDKInfo.cpp
clang/lib/Driver/ToolChains/Darwin.cpp
clang/unittests/Basic/CMakeLists.txt
clang/unittests/Basic/DarwinSDKinfoTest.cpp [new file with mode: 0644]
llvm/include/llvm/Support/VersionTuple.h

index f4428f2..f689226 100644 (file)
 #define LLVM_CLANG_BASIC_DARWIN_SDK_INFO_H
 
 #include "clang/Basic/LLVM.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/Triple.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/VersionTuple.h"
 #include "llvm/Support/VirtualFileSystem.h"
 
+namespace llvm {
+namespace json {
+class Object;
+} // end namespace json
+} // end namespace llvm
+
 namespace clang {
 
 /// The information about the darwin SDK that was used during this compilation.
 class DarwinSDKInfo {
 public:
-  DarwinSDKInfo(llvm::VersionTuple Version) : Version(Version) {}
+  /// A value that describes two os-environment pairs that can be used as a key
+  /// to the version map in the SDK.
+  struct OSEnvPair {
+  public:
+    using StorageType = uint64_t;
+
+    constexpr OSEnvPair(llvm::Triple::OSType FromOS,
+                        llvm::Triple::EnvironmentType FromEnv,
+                        llvm::Triple::OSType ToOS,
+                        llvm::Triple::EnvironmentType ToEnv)
+        : Value(((StorageType(FromOS) * StorageType(llvm::Triple::LastOSType) +
+                  StorageType(FromEnv))
+                 << 32ull) |
+                (StorageType(ToOS) * StorageType(llvm::Triple::LastOSType) +
+                 StorageType(ToEnv))) {}
+
+    /// Returns the os-environment mapping pair that's used to represent the
+    /// macOS -> Mac Catalyst version mapping.
+    static inline constexpr OSEnvPair macOStoMacCatalystPair() {
+      return OSEnvPair(llvm::Triple::MacOSX, llvm::Triple::UnknownEnvironment,
+                       llvm::Triple::IOS, llvm::Triple::MacABI);
+    }
+
+  private:
+    StorageType Value;
+
+    friend class DarwinSDKInfo;
+  };
+
+  /// Represents a version mapping that maps from a version of one target to a
+  /// version of a related target.
+  ///
+  /// e.g. "macOS_iOSMac":{"10.15":"13.1"} is an example of a macOS -> Mac
+  /// Catalyst version map.
+  class RelatedTargetVersionMapping {
+  public:
+    RelatedTargetVersionMapping(
+        VersionTuple MinimumKeyVersion, VersionTuple MaximumKeyVersion,
+        VersionTuple MinimumValue, VersionTuple MaximumValue,
+        llvm::DenseMap<VersionTuple, VersionTuple> Mapping)
+        : MinimumKeyVersion(MinimumKeyVersion),
+          MaximumKeyVersion(MaximumKeyVersion), MinimumValue(MinimumValue),
+          MaximumValue(MaximumValue), Mapping(Mapping) {
+      assert(!this->Mapping.empty() && "unexpected empty mapping");
+    }
+
+    /// Returns the value with the lowest version in the mapping.
+    const VersionTuple &getMinimumValue() const { return MinimumValue; }
+
+    /// Returns the mapped key, or the appropriate Minimum / MaximumValue if
+    /// they key is outside of the mapping bounds. If they key isn't mapped, but
+    /// within the minimum and maximum bounds, None is returned.
+    Optional<VersionTuple> map(const VersionTuple &Key,
+                               const VersionTuple &MinimumValue,
+                               Optional<VersionTuple> MaximumValue) const;
+
+    static Optional<RelatedTargetVersionMapping>
+    parseJSON(const llvm::json::Object &Obj,
+              VersionTuple MaximumDeploymentTarget);
+
+  private:
+    VersionTuple MinimumKeyVersion;
+    VersionTuple MaximumKeyVersion;
+    VersionTuple MinimumValue;
+    VersionTuple MaximumValue;
+    llvm::DenseMap<VersionTuple, VersionTuple> Mapping;
+  };
+
+  DarwinSDKInfo(VersionTuple Version, VersionTuple MaximumDeploymentTarget,
+                llvm::DenseMap<OSEnvPair::StorageType,
+                               Optional<RelatedTargetVersionMapping>>
+                    VersionMappings =
+                        llvm::DenseMap<OSEnvPair::StorageType,
+                                       Optional<RelatedTargetVersionMapping>>())
+      : Version(Version), MaximumDeploymentTarget(MaximumDeploymentTarget),
+        VersionMappings(std::move(VersionMappings)) {}
 
   const llvm::VersionTuple &getVersion() const { return Version; }
 
+  // Returns the optional, target-specific version mapping that maps from one
+  // target to another target.
+  //
+  // This mapping is constructed from an appropriate mapping in the SDKSettings,
+  // for instance, when building for Mac Catalyst, the mapping would contain the
+  // "macOS_iOSMac" mapping as it maps the macOS versions to the Mac Catalyst
+  // versions.
+  //
+  // This mapping does not exist when the target doesn't have an appropriate
+  // related version mapping, or when there was an error reading the mapping
+  // from the SDKSettings, or when it's missing in the SDKSettings.
+  const RelatedTargetVersionMapping *getVersionMapping(OSEnvPair Kind) const {
+    auto Mapping = VersionMappings.find(Kind.Value);
+    if (Mapping == VersionMappings.end())
+      return nullptr;
+    return Mapping->getSecond().hasValue() ? Mapping->getSecond().getPointer()
+                                           : nullptr;
+  }
+
+  static Optional<DarwinSDKInfo>
+  parseDarwinSDKSettingsJSON(const llvm::json::Object *Obj);
+
 private:
-  llvm::VersionTuple Version;
+  VersionTuple Version;
+  VersionTuple MaximumDeploymentTarget;
+  // Need to wrap the value in an optional here as the value has to be default
+  // constructible, and std::unique_ptr doesn't like DarwinSDKInfo being
+  // Optional as Optional is trying to copy it in emplace.
+  llvm::DenseMap<OSEnvPair::StorageType, Optional<RelatedTargetVersionMapping>>
+      VersionMappings;
 };
 
 /// Parse the SDK information from the SDKSettings.json file.
index e2ea580..6959b84 100644 (file)
 
 using namespace clang;
 
+Optional<VersionTuple> DarwinSDKInfo::RelatedTargetVersionMapping::map(
+    const VersionTuple &Key, const VersionTuple &MinimumValue,
+    Optional<VersionTuple> MaximumValue) const {
+  if (Key < MinimumKeyVersion)
+    return MinimumValue;
+  if (Key > MaximumKeyVersion)
+    return MaximumValue;
+  auto KV = Mapping.find(Key.normalize());
+  if (KV != Mapping.end())
+    return KV->getSecond();
+  // If no exact entry found, try just the major key version. Only do so when
+  // a minor version number is present, to avoid recursing indefinitely into
+  // the major-only check.
+  if (Key.getMinor())
+    return map(VersionTuple(Key.getMajor()), MinimumValue, MaximumValue);
+  // If this a major only key, return None for a missing entry.
+  return None;
+}
+
+Optional<DarwinSDKInfo::RelatedTargetVersionMapping>
+DarwinSDKInfo::RelatedTargetVersionMapping::parseJSON(
+    const llvm::json::Object &Obj, VersionTuple MaximumDeploymentTarget) {
+  VersionTuple Min = VersionTuple(std::numeric_limits<unsigned>::max());
+  VersionTuple Max = VersionTuple(0);
+  VersionTuple MinValue = Min;
+  llvm::DenseMap<VersionTuple, VersionTuple> Mapping;
+  for (const auto &KV : Obj) {
+    if (auto Val = KV.getSecond().getAsString()) {
+      llvm::VersionTuple KeyVersion;
+      llvm::VersionTuple ValueVersion;
+      if (KeyVersion.tryParse(KV.getFirst()) || ValueVersion.tryParse(*Val))
+        return None;
+      Mapping[KeyVersion.normalize()] = ValueVersion;
+      if (KeyVersion < Min)
+        Min = KeyVersion;
+      if (KeyVersion > Max)
+        Max = KeyVersion;
+      if (ValueVersion < MinValue)
+        MinValue = ValueVersion;
+    }
+  }
+  if (Mapping.empty())
+    return None;
+  return RelatedTargetVersionMapping(
+      Min, Max, MinValue, MaximumDeploymentTarget, std::move(Mapping));
+}
+
+static Optional<VersionTuple> getVersionKey(const llvm::json::Object &Obj,
+                                            StringRef Key) {
+  auto Value = Obj.getString(Key);
+  if (!Value)
+    return None;
+  VersionTuple Version;
+  if (Version.tryParse(*Value))
+    return None;
+  return Version;
+}
+
+Optional<DarwinSDKInfo>
+DarwinSDKInfo::parseDarwinSDKSettingsJSON(const llvm::json::Object *Obj) {
+  auto Version = getVersionKey(*Obj, "Version");
+  if (!Version)
+    return None;
+  auto MaximumDeploymentVersion =
+      getVersionKey(*Obj, "MaximumDeploymentTarget");
+  if (!MaximumDeploymentVersion)
+    return None;
+  llvm::DenseMap<OSEnvPair::StorageType, Optional<RelatedTargetVersionMapping>>
+      VersionMappings;
+  if (const auto *VM = Obj->getObject("VersionMap")) {
+    if (const auto *Mapping = VM->getObject("macOS_iOSMac")) {
+      auto VersionMap = RelatedTargetVersionMapping::parseJSON(
+          *Mapping, *MaximumDeploymentVersion);
+      if (!VersionMap)
+        return None;
+      VersionMappings[OSEnvPair::macOStoMacCatalystPair().Value] =
+          std::move(VersionMap);
+    }
+  }
+
+  return DarwinSDKInfo(std::move(*Version),
+                       std::move(*MaximumDeploymentVersion),
+                       std::move(VersionMappings));
+}
+
 Expected<Optional<DarwinSDKInfo>>
 clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) {
   llvm::SmallString<256> Filepath = SDKRootPath;
@@ -30,11 +115,12 @@ clang::parseDarwinSDKInfo(llvm::vfs::FileSystem &VFS, StringRef SDKRootPath) {
     return Result.takeError();
 
   if (const auto *Obj = Result->getAsObject()) {
+    // FIXME: Switch to use parseDarwinSDKSettingsJSON.
     auto VersionString = Obj->getString("Version");
     if (VersionString) {
       VersionTuple Version;
       if (!Version.tryParse(*VersionString))
-        return DarwinSDKInfo(Version);
+        return DarwinSDKInfo(Version, Version);
     }
   }
   return llvm::make_error<llvm::StringError>("invalid SDKSettings.json",
index 0f41c0f..fc1f101 100644 (file)
@@ -1506,7 +1506,9 @@ struct DarwinPlatform {
     bool IsValid = !Version.tryParse(OSVersion);
     (void)IsValid;
     assert(IsValid && "invalid SDK version");
-    return DarwinSDKInfo(Version);
+    return DarwinSDKInfo(
+        Version,
+        /*MaximumDeploymentTarget=*/VersionTuple(Version.getMajor(), 0, 99));
   }
 
 private:
index 9d23a06..cbb18ca 100644 (file)
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_unittest(BasicTests
   CharInfoTest.cpp
+  DarwinSDKInfoTest.cpp
   DiagnosticTest.cpp
   FileEntryTest.cpp
   FileManagerTest.cpp
diff --git a/clang/unittests/Basic/DarwinSDKinfoTest.cpp b/clang/unittests/Basic/DarwinSDKinfoTest.cpp
new file mode 100644 (file)
index 0000000..f845e15
--- /dev/null
@@ -0,0 +1,66 @@
+//===- unittests/Basic/DarwinSDKInfoTest.cpp -- SDKSettings.json test -----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Basic/DarwinSDKInfo.h"
+#include "llvm/Support/JSON.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace clang;
+
+TEST(DarwinSDKInfoTest, ParseAndTestMapping) {
+  llvm::json::Object Obj;
+  Obj["Version"] = "11.0";
+  Obj["MaximumDeploymentTarget"] = "11.99";
+  llvm::json::Object VersionMap;
+  VersionMap["10.15"] = "13.1";
+  VersionMap["11.0"] = "14.0";
+  VersionMap["11.2"] = "14.2";
+  llvm::json::Object MacOS2iOSMac;
+  MacOS2iOSMac["macOS_iOSMac"] = std::move(VersionMap);
+  Obj["VersionMap"] = std::move(MacOS2iOSMac);
+
+  auto SDKInfo = DarwinSDKInfo::parseDarwinSDKSettingsJSON(&Obj);
+  ASSERT_TRUE(SDKInfo);
+  EXPECT_EQ(SDKInfo->getVersion(), VersionTuple(11, 0));
+
+  auto Mapping = SDKInfo->getVersionMapping(
+      DarwinSDKInfo::OSEnvPair::macOStoMacCatalystPair());
+  ASSERT_TRUE(Mapping);
+  // Verify that the macOS versions that are present in the map are translated
+  // directly to their corresponding Mac Catalyst versions.
+  EXPECT_EQ(*Mapping->map(VersionTuple(10, 15), VersionTuple(), None),
+            VersionTuple(13, 1));
+  EXPECT_EQ(*Mapping->map(VersionTuple(11, 0), VersionTuple(), None),
+            VersionTuple(14, 0));
+  EXPECT_EQ(*Mapping->map(VersionTuple(11, 2), VersionTuple(), None),
+            VersionTuple(14, 2));
+
+  // Verify that a macOS version that's not present in the map is translated
+  // like the nearest major OS version.
+  EXPECT_EQ(*Mapping->map(VersionTuple(11, 1), VersionTuple(), None),
+            VersionTuple(14, 0));
+
+  // Verify that the macOS versions that are outside of the mapped version
+  // range map to the min/max values passed to the `map` call.
+  EXPECT_EQ(*Mapping->map(VersionTuple(10, 14), VersionTuple(99, 99), None),
+            VersionTuple(99, 99));
+  EXPECT_EQ(
+      *Mapping->map(VersionTuple(11, 5), VersionTuple(), VersionTuple(99, 99)),
+      VersionTuple(99, 99));
+  EXPECT_EQ(*Mapping->map(VersionTuple(11, 5), VersionTuple(99, 98),
+                          VersionTuple(99, 99)),
+            VersionTuple(99, 99));
+}
+
+TEST(DarwinSDKInfoTest, MissingKeys) {
+  llvm::json::Object Obj;
+  ASSERT_FALSE(DarwinSDKInfo::parseDarwinSDKSettingsJSON(&Obj));
+  Obj["Version"] = "11.0";
+  ASSERT_FALSE(DarwinSDKInfo::parseDarwinSDKSettingsJSON(&Obj));
+}
index 6f3711f..a48ae0b 100644 (file)
@@ -14,6 +14,7 @@
 #ifndef LLVM_SUPPORT_VERSIONTUPLE_H
 #define LLVM_SUPPORT_VERSIONTUPLE_H
 
+#include "llvm/ADT/DenseMapInfo.h"
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/Optional.h"
 #include <string>
@@ -95,6 +96,20 @@ public:
     return *this;
   }
 
+  /// 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;
+      }
+    }
+    return Result;
+  }
+
   /// 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) {
@@ -161,5 +176,28 @@ public:
 /// Print a version number.
 raw_ostream &operator<<(raw_ostream &Out, const VersionTuple &V);
 
+// Provide DenseMapInfo for version tuples.
+template <> struct DenseMapInfo<VersionTuple> {
+  static inline VersionTuple getEmptyKey() { return VersionTuple(0x7FFFFFFF); }
+  static inline VersionTuple getTombstoneKey() {
+    return VersionTuple(0x7FFFFFFE);
+  }
+  static unsigned getHashValue(const VersionTuple &Value) {
+    unsigned Result = Value.getMajor();
+    if (auto Minor = Value.getMinor())
+      Result = detail::combineHashValue(Result, *Minor);
+    if (auto Subminor = Value.getSubminor())
+      Result = detail::combineHashValue(Result, *Subminor);
+    if (auto Build = Value.getBuild())
+      Result = detail::combineHashValue(Result, *Build);
+
+    return Result;
+  }
+
+  static bool isEqual(const VersionTuple &LHS, const VersionTuple &RHS) {
+    return LHS == RHS;
+  }
+};
+
 } // end namespace llvm
 #endif // LLVM_SUPPORT_VERSIONTUPLE_H