[clang] 701456b - [darwin] add support for __isPlatformVersionAtLeast check for if (@available)

Alex Lorenz via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 2 16:28:30 PST 2020


Author: Alex Lorenz
Date: 2020-11-02T16:28:09-08:00
New Revision: 701456b52355c25089e1b536805164570f5def6f

URL: https://github.com/llvm/llvm-project/commit/701456b52355c25089e1b536805164570f5def6f
DIFF: https://github.com/llvm/llvm-project/commit/701456b52355c25089e1b536805164570f5def6f.diff

LOG: [darwin] add support for __isPlatformVersionAtLeast check for if (@available)

The __isPlatformVersionAtLeast routine is an implementation of `if (@available)` check
that uses the _availability_version_check API on Darwin that's supported on
macOS 10.15, iOS 13, tvOS 13 and watchOS 6.

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

Added: 
    compiler-rt/test/builtins/TestCases/Darwin/platform_version_check_test.c

Modified: 
    clang/lib/CodeGen/CGExprScalar.cpp
    clang/lib/CodeGen/CGObjC.cpp
    clang/lib/CodeGen/CodeGenFunction.h
    clang/lib/CodeGen/CodeGenModule.h
    clang/test/CodeGenObjC/availability-cf-link-guard.m
    clang/test/CodeGenObjC/availability-check.m
    compiler-rt/lib/builtins/os_version_check.c

Removed: 
    


################################################################################
diff  --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index f14f862a5f67..ab2bb2de1439 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -529,14 +529,7 @@ class ScalarExprEmitter
     if (Version <= CGF.CGM.getTarget().getPlatformMinVersion())
       return llvm::ConstantInt::get(Builder.getInt1Ty(), 1);
 
-    Optional<unsigned> Min = Version.getMinor(), SMin = Version.getSubminor();
-    llvm::Value *Args[] = {
-        llvm::ConstantInt::get(CGF.CGM.Int32Ty, Version.getMajor()),
-        llvm::ConstantInt::get(CGF.CGM.Int32Ty, Min ? *Min : 0),
-        llvm::ConstantInt::get(CGF.CGM.Int32Ty, SMin ? *SMin : 0),
-    };
-
-    return CGF.EmitBuiltinAvailable(Args);
+    return CGF.EmitBuiltinAvailable(Version);
   }
 
   Value *VisitArraySubscriptExpr(ArraySubscriptExpr *E);

diff  --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index a64712898b70..89bf402c19f8 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -23,6 +23,7 @@
 #include "clang/Basic/Diagnostic.h"
 #include "clang/CodeGen/CGFunctionInfo.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/BinaryFormat/MachO.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/InlineAsm.h"
 using namespace clang;
@@ -3814,9 +3815,61 @@ CodeGenFunction::EmitBlockCopyAndAutorelease(llvm::Value *Block, QualType Ty) {
   return Val;
 }
 
+static unsigned getBaseMachOPlatformID(const llvm::Triple &TT) {
+  switch (TT.getOS()) {
+  case llvm::Triple::Darwin:
+  case llvm::Triple::MacOSX:
+    return llvm::MachO::PLATFORM_MACOS;
+  case llvm::Triple::IOS:
+    return llvm::MachO::PLATFORM_IOS;
+  case llvm::Triple::TvOS:
+    return llvm::MachO::PLATFORM_TVOS;
+  case llvm::Triple::WatchOS:
+    return llvm::MachO::PLATFORM_WATCHOS;
+  default:
+    return /*Unknown platform*/ 0;
+  }
+}
+
+static llvm::Value *emitIsPlatformVersionAtLeast(CodeGenFunction &CGF,
+                                                 const VersionTuple &Version) {
+  CodeGenModule &CGM = CGF.CGM;
+  // Note: we intend to support multi-platform version checks, so reserve
+  // the room for a dual platform checking invocation that will be
+  // implemented in the future.
+  llvm::SmallVector<llvm::Value *, 8> Args;
+
+  auto EmitArgs = [&](const VersionTuple &Version, const llvm::Triple &TT) {
+    Optional<unsigned> Min = Version.getMinor(), SMin = Version.getSubminor();
+    Args.push_back(
+        llvm::ConstantInt::get(CGM.Int32Ty, getBaseMachOPlatformID(TT)));
+    Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, Version.getMajor()));
+    Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, Min ? *Min : 0));
+    Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, SMin ? *SMin : 0));
+  };
+
+  assert(!Version.empty() && "unexpected empty version");
+  EmitArgs(Version, CGM.getTarget().getTriple());
+
+  if (!CGM.IsPlatformVersionAtLeastFn) {
+    llvm::FunctionType *FTy = llvm::FunctionType::get(
+        CGM.Int32Ty, {CGM.Int32Ty, CGM.Int32Ty, CGM.Int32Ty, CGM.Int32Ty},
+        false);
+    CGM.IsPlatformVersionAtLeastFn =
+        CGM.CreateRuntimeFunction(FTy, "__isPlatformVersionAtLeast");
+  }
+
+  llvm::Value *Check =
+      CGF.EmitNounwindRuntimeCall(CGM.IsPlatformVersionAtLeastFn, Args);
+  return CGF.Builder.CreateICmpNE(Check,
+                                  llvm::Constant::getNullValue(CGM.Int32Ty));
+}
+
 llvm::Value *
-CodeGenFunction::EmitBuiltinAvailable(ArrayRef<llvm::Value *> Args) {
-  assert(Args.size() == 3 && "Expected 3 argument here!");
+CodeGenFunction::EmitBuiltinAvailable(const VersionTuple &Version) {
+  // Darwin uses the new __isPlatformVersionAtLeast family of routines.
+  if (CGM.getTarget().getTriple().isOSDarwin())
+    return emitIsPlatformVersionAtLeast(*this, Version);
 
   if (!CGM.IsOSVersionAtLeastFn) {
     llvm::FunctionType *FTy =
@@ -3825,18 +3878,51 @@ CodeGenFunction::EmitBuiltinAvailable(ArrayRef<llvm::Value *> Args) {
         CGM.CreateRuntimeFunction(FTy, "__isOSVersionAtLeast");
   }
 
+  Optional<unsigned> Min = Version.getMinor(), SMin = Version.getSubminor();
+  llvm::Value *Args[] = {
+      llvm::ConstantInt::get(CGM.Int32Ty, Version.getMajor()),
+      llvm::ConstantInt::get(CGM.Int32Ty, Min ? *Min : 0),
+      llvm::ConstantInt::get(CGM.Int32Ty, SMin ? *SMin : 0),
+  };
+
   llvm::Value *CallRes =
       EmitNounwindRuntimeCall(CGM.IsOSVersionAtLeastFn, Args);
 
   return Builder.CreateICmpNE(CallRes, llvm::Constant::getNullValue(Int32Ty));
 }
 
+static bool isFoundationNeededForDarwinAvailabilityCheck(
+    const llvm::Triple &TT, const VersionTuple &TargetVersion) {
+  VersionTuple FoundationDroppedInVersion;
+  switch (TT.getOS()) {
+  case llvm::Triple::IOS:
+  case llvm::Triple::TvOS:
+    FoundationDroppedInVersion = VersionTuple(/*Major=*/13);
+    break;
+  case llvm::Triple::WatchOS:
+    FoundationDroppedInVersion = VersionTuple(/*Major=*/6);
+    break;
+  case llvm::Triple::Darwin:
+  case llvm::Triple::MacOSX:
+    FoundationDroppedInVersion = VersionTuple(/*Major=*/10, /*Minor=*/15);
+    break;
+  default:
+    llvm_unreachable("Unexpected OS");
+  }
+  return TargetVersion < FoundationDroppedInVersion;
+}
+
 void CodeGenModule::emitAtAvailableLinkGuard() {
-  if (!IsOSVersionAtLeastFn)
+  if (!IsPlatformVersionAtLeastFn)
     return;
   // @available requires CoreFoundation only on Darwin.
   if (!Target.getTriple().isOSDarwin())
     return;
+  // @available doesn't need Foundation on macOS 10.15+, iOS/tvOS 13+, or
+  // watchOS 6+.
+  if (!isFoundationNeededForDarwinAvailabilityCheck(
+          Target.getTriple(), Target.getPlatformMinVersion()))
+    return;
   // Add -framework CoreFoundation to the linker commands. We still want to
   // emit the core foundation reference down below because otherwise if
   // CoreFoundation is not used in the code, the linker won't link the

diff  --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 4cf857a5a3ec..346253a68cbf 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4102,7 +4102,7 @@ class CodeGenFunction : public CodeGenTypeCache {
 public:
   llvm::Value *EmitMSVCBuiltinExpr(MSVCIntrin BuiltinID, const CallExpr *E);
 
-  llvm::Value *EmitBuiltinAvailable(ArrayRef<llvm::Value *> Args);
+  llvm::Value *EmitBuiltinAvailable(const VersionTuple &Version);
 
   llvm::Value *EmitObjCProtocolExpr(const ObjCProtocolExpr *E);
   llvm::Value *EmitObjCStringLiteral(const ObjCStringLiteral *E);

diff  --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index cb5b9bda3fd8..8996805145fd 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -606,9 +606,11 @@ class CodeGenModule : public CodeGenTypeCache {
     return *ObjCData;
   }
 
-  // Version checking function, used to implement ObjC's @available:
+  // Version checking functions, used to implement ObjC's @available:
   // i32 @__isOSVersionAtLeast(i32, i32, i32)
   llvm::FunctionCallee IsOSVersionAtLeastFn = nullptr;
+  // i32 @__isPlatformVersionAtLeast(i32, i32, i32, i32)
+  llvm::FunctionCallee IsPlatformVersionAtLeastFn = nullptr;
 
   InstrProfStats &getPGOStats() { return PGOStats; }
   llvm::IndexedInstrProfReader *getPGOReader() const { return PGOReader.get(); }

diff  --git a/clang/test/CodeGenObjC/availability-cf-link-guard.m b/clang/test/CodeGenObjC/availability-cf-link-guard.m
index 6bd426476bba..54ad19c9640a 100644
--- a/clang/test/CodeGenObjC/availability-cf-link-guard.m
+++ b/clang/test/CodeGenObjC/availability-cf-link-guard.m
@@ -3,6 +3,13 @@
 // RUN: %clang_cc1 -triple x86_64-apple-macosx10.11 -emit-llvm -o - -D DEF_CF %s | FileCheck --check-prefixes=CHECK_CF,CHECK_LINK_OPT %s
 // RUN: %clang_cc1 -triple x86_64-apple-macosx10.12 -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
 // RUN: %clang_cc1 -triple x86_64-unknown-linux -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
+// RUN: %clang_cc1 -triple x86_64-apple-macos10.15 -DCHECK_OS="macos 10.15.1" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
+// RUN: %clang_cc1 -triple arm64-apple-ios13.0 -DCHECK_OS="ios 14" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
+// RUN: %clang_cc1 -triple arm64-apple-tvos13.0 -DCHECK_OS="tvos 14" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
+// RUN: %clang_cc1 -triple arm64-apple-watchos6.0 -DCHECK_OS="watchos 7" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
+// RUN: %clang_cc1 -triple arm64-apple-ios12.0 -DCHECK_OS="ios 13" -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CHECK_LINK_OPT %s
+// RUN: %clang_cc1 -triple arm64-apple-tvos12.0 -DCHECK_OS="tvos 13" -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CHECK_LINK_OPT %s
+// RUN: %clang_cc1 -triple arm64-apple-watchos5.0 -DCHECK_OS="watchos 6" -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CHECK_LINK_OPT %s
 
 #ifdef DEF_CF
 struct CFBundle;
@@ -13,15 +20,19 @@
 // CHECK_CF-NEXT: call {{.*}}@CFBundleGetVersionNumber
 #endif
 
+#ifndef CHECK_OS
+#define CHECK_OS macos 10.12
+#endif
+
 void use_at_available() {
 #ifdef DEF_CF
   CFBundleGetVersionNumber(0);
 #endif
 #ifdef USE_BUILTIN
-  if (__builtin_available(macos 10.12, *))
+  if (__builtin_available(CHECK_OS, *))
     ;
 #else
-  if (@available(macos 10.12, *))
+  if (@available(CHECK_OS, *))
     ;
 #endif
 }

diff  --git a/clang/test/CodeGenObjC/availability-check.m b/clang/test/CodeGenObjC/availability-check.m
index 71c5ff77c96a..518afc4d3345 100644
--- a/clang/test/CodeGenObjC/availability-check.m
+++ b/clang/test/CodeGenObjC/availability-check.m
@@ -1,31 +1,31 @@
 // RUN: %clang_cc1 -triple x86_64-apple-macosx10.11 -emit-llvm -o - %s | FileCheck %s
 
 void use_at_available() {
-  // CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 12, i32 0)
+  // CHECK: call i32 @__isPlatformVersionAtLeast(i32 1, i32 10, i32 12, i32 0)
   // CHECK-NEXT: icmp ne
   if (__builtin_available(macos 10.12, *))
     ;
 
-  // CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 12, i32 0)
+  // CHECK: call i32 @__isPlatformVersionAtLeast(i32 1, i32 10, i32 12, i32 0)
   // CHECK-NEXT: icmp ne
   if (@available(macos 10.12, *))
     ;
 
-  // CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 12, i32 42)
+  // CHECK: call i32 @__isPlatformVersionAtLeast(i32 1, i32 10, i32 12, i32 42)
   // CHECK-NEXT: icmp ne
   if (__builtin_available(ios 10, macos 10.12.42, *))
     ;
 
-  // CHECK-NOT: call i32 @__isOSVersionAtLeast
+  // CHECK-NOT: call i32 @__isPlatformVersionAtLeast
   // CHECK: br i1 true
   if (__builtin_available(ios 10, *))
     ;
 
   // This check should be folded: our deployment target is 10.11.
-  // CHECK-NOT: call i32 @__isOSVersionAtLeast
+  // CHECK-NOT: call i32 @__isPlatformVersionAtLeast
   // CHECK: br i1 true
   if (__builtin_available(macos 10.11, *))
     ;
 }
 
-// CHECK: declare i32 @__isOSVersionAtLeast(i32, i32, i32)
+// CHECK: declare i32 @__isPlatformVersionAtLeast(i32, i32, i32, i32)

diff  --git a/compiler-rt/lib/builtins/os_version_check.c b/compiler-rt/lib/builtins/os_version_check.c
index fbc68f58caf7..d7194b99ae54 100644
--- a/compiler-rt/lib/builtins/os_version_check.c
+++ b/compiler-rt/lib/builtins/os_version_check.c
@@ -24,6 +24,20 @@
 // These three variables hold the host's OS version.
 static int32_t GlobalMajor, GlobalMinor, GlobalSubminor;
 static dispatch_once_t DispatchOnceCounter;
+static dispatch_once_t CompatibilityDispatchOnceCounter;
+
+// _availability_version_check darwin API support.
+typedef uint32_t dyld_platform_t;
+
+typedef struct {
+  dyld_platform_t platform;
+  uint32_t version;
+} dyld_build_version_t;
+
+typedef bool (*AvailabilityVersionCheckFuncTy)(uint32_t count,
+                                               dyld_build_version_t versions[]);
+
+static AvailabilityVersionCheckFuncTy AvailabilityVersionCheck;
 
 // We can't include <CoreFoundation/CoreFoundation.h> directly from here, so
 // just forward declare everything that we need from it.
@@ -72,9 +86,25 @@ typedef Boolean (*CFStringGetCStringFuncTy)(CFStringRef, char *, CFIndex,
                                             CFStringEncoding);
 typedef void (*CFReleaseFuncTy)(CFTypeRef);
 
-// Find and parse the SystemVersion.plist file.
-static void parseSystemVersionPList(void *Unused) {
-  (void)Unused;
+static void _initializeAvailabilityCheck(bool LoadPlist) {
+  if (AvailabilityVersionCheck && !LoadPlist) {
+    // New API is supported and we're not being asked to load the plist,
+    // exit early!
+    return;
+  }
+
+  // Use the new API if it's is available.
+  AvailabilityVersionCheck = (AvailabilityVersionCheckFuncTy)dlsym(
+      RTLD_DEFAULT, "_availability_version_check");
+
+  if (AvailabilityVersionCheck && !LoadPlist) {
+    // New API is supported and we're not being asked to load the plist,
+    // exit early!
+    return;
+  }
+  // Still load the PLIST to ensure that the existing calls to
+  // __isOSVersionAtLeast still work even with new compiler-rt and old OSes.
+
   // Load CoreFoundation dynamically
   const void *NullAllocator = dlsym(RTLD_DEFAULT, "kCFAllocatorNull");
   if (!NullAllocator)
@@ -201,9 +231,24 @@ static void parseSystemVersionPList(void *Unused) {
   fclose(PropertyList);
 }
 
+// Find and parse the SystemVersion.plist file.
+static void compatibilityInitializeAvailabilityCheck(void *Unused) {
+  (void)Unused;
+  _initializeAvailabilityCheck(/*LoadPlist=*/true);
+}
+
+static void initializeAvailabilityCheck(void *Unused) {
+  (void)Unused;
+  _initializeAvailabilityCheck(/*LoadPlist=*/false);
+}
+
+// This old API entry point is no longer used by Clang for Darwin. We still need
+// to keep it around to ensure that object files that reference it are still
+// usable when linked with new compiler-rt.
 int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
   // Populate the global version variables, if they haven't already.
-  dispatch_once_f(&DispatchOnceCounter, NULL, parseSystemVersionPList);
+  dispatch_once_f(&CompatibilityDispatchOnceCounter, NULL,
+                  compatibilityInitializeAvailabilityCheck);
 
   if (Major < GlobalMajor)
     return 1;
@@ -216,6 +261,23 @@ int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
   return Subminor <= GlobalSubminor;
 }
 
+static inline uint32_t ConstructVersion(uint32_t Major, uint32_t Minor,
+                                        uint32_t Subminor) {
+  return ((Major & 0xffff) << 16) | ((Minor & 0xff) << 8) | (Subminor & 0xff);
+}
+
+int32_t __isPlatformVersionAtLeast(uint32_t Platform, uint32_t Major,
+                                   uint32_t Minor, uint32_t Subminor) {
+  dispatch_once_f(&DispatchOnceCounter, NULL, initializeAvailabilityCheck);
+
+  if (!AvailabilityVersionCheck) {
+    return __isOSVersionAtLeast(Major, Minor, Subminor);
+  }
+  dyld_build_version_t Versions[] = {
+      {Platform, ConstructVersion(Major, Minor, Subminor)}};
+  return AvailabilityVersionCheck(1, Versions);
+}
+
 #elif __ANDROID__
 
 #include <pthread.h>

diff  --git a/compiler-rt/test/builtins/TestCases/Darwin/platform_version_check_test.c b/compiler-rt/test/builtins/TestCases/Darwin/platform_version_check_test.c
new file mode 100644
index 000000000000..8e56fd91dd61
--- /dev/null
+++ b/compiler-rt/test/builtins/TestCases/Darwin/platform_version_check_test.c
@@ -0,0 +1,31 @@
+// RUN: %clang %s -o %t -mmacosx-version-min=10.6 -framework CoreFoundation -DMAJOR=%macos_version_major -DMINOR=%macos_version_minor -DSUBMINOR=%macos_version_subminor
+// RUN: %run %t
+
+typedef int int32_t;
+typedef unsigned int uint32_t;
+
+int32_t __isPlatformVersionAtLeast(uint32_t Platform, uint32_t Major,
+                                   uint32_t Minor, uint32_t Subminor);
+
+#define PLATFORM_MACOS 1
+
+int32_t check(uint32_t Major, uint32_t Minor, uint32_t Subminor) {
+  int32_t Result =
+      __isPlatformVersionAtLeast(PLATFORM_MACOS, Major, Minor, Subminor);
+  return Result;
+}
+
+int main() {
+  if (!check(MAJOR, MINOR, SUBMINOR))
+    return 1;
+  if (check(MAJOR, MINOR, SUBMINOR + 1))
+    return 1;
+  if (SUBMINOR && check(MAJOR + 1, MINOR, SUBMINOR - 1))
+    return 1;
+  if (SUBMINOR && !check(MAJOR, MINOR, SUBMINOR - 1))
+    return 1;
+  if (MAJOR && !check(MAJOR - 1, MINOR + 1, SUBMINOR))
+    return 1;
+
+  return 0;
+}


        


More information about the cfe-commits mailing list