[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:
Alex Lorenz 2020-10-28 22:48:59 -07:00
parent 5a829ef6ad
commit 701456b523
8 changed files with 210 additions and 25 deletions

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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(); }

View File

@ -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
}

View File

@ -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)

View File

@ -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>

View File

@ -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;
}