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
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);
#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;
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 =
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
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);
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(); }
// 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;
// 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
}
// 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)
// 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.
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)
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;
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>
--- /dev/null
+// 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;
+}