[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
This commit is contained in:
parent
5a829ef6ad
commit
701456b523
|
@ -529,14 +529,7 @@ public:
|
|||
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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4102,7 +4102,7 @@ private:
|
|||
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);
|
||||
|
|
|
@ -606,9 +606,11 @@ public:
|
|||
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(); }
|
||||
|
|
|
@ -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 @@ unsigned CFBundleGetVersionNumber(CFBundleRef bundle);
|
|||
// 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 @@ Fail:
|
|||
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>
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue