Skip to content

Commit 701456b

Browse files
committed
[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
1 parent 5a829ef commit 701456b

File tree

8 files changed

+210
-25
lines changed

8 files changed

+210
-25
lines changed

clang/lib/CodeGen/CGExprScalar.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -529,14 +529,7 @@ class ScalarExprEmitter
529529
if (Version <= CGF.CGM.getTarget().getPlatformMinVersion())
530530
return llvm::ConstantInt::get(Builder.getInt1Ty(), 1);
531531

532-
Optional<unsigned> Min = Version.getMinor(), SMin = Version.getSubminor();
533-
llvm::Value *Args[] = {
534-
llvm::ConstantInt::get(CGF.CGM.Int32Ty, Version.getMajor()),
535-
llvm::ConstantInt::get(CGF.CGM.Int32Ty, Min ? *Min : 0),
536-
llvm::ConstantInt::get(CGF.CGM.Int32Ty, SMin ? *SMin : 0),
537-
};
538-
539-
return CGF.EmitBuiltinAvailable(Args);
532+
return CGF.EmitBuiltinAvailable(Version);
540533
}
541534

542535
Value *VisitArraySubscriptExpr(ArraySubscriptExpr *E);

clang/lib/CodeGen/CGObjC.cpp

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "clang/Basic/Diagnostic.h"
2424
#include "clang/CodeGen/CGFunctionInfo.h"
2525
#include "llvm/ADT/STLExtras.h"
26+
#include "llvm/BinaryFormat/MachO.h"
2627
#include "llvm/IR/DataLayout.h"
2728
#include "llvm/IR/InlineAsm.h"
2829
using namespace clang;
@@ -3814,9 +3815,61 @@ CodeGenFunction::EmitBlockCopyAndAutorelease(llvm::Value *Block, QualType Ty) {
38143815
return Val;
38153816
}
38163817

3818+
static unsigned getBaseMachOPlatformID(const llvm::Triple &TT) {
3819+
switch (TT.getOS()) {
3820+
case llvm::Triple::Darwin:
3821+
case llvm::Triple::MacOSX:
3822+
return llvm::MachO::PLATFORM_MACOS;
3823+
case llvm::Triple::IOS:
3824+
return llvm::MachO::PLATFORM_IOS;
3825+
case llvm::Triple::TvOS:
3826+
return llvm::MachO::PLATFORM_TVOS;
3827+
case llvm::Triple::WatchOS:
3828+
return llvm::MachO::PLATFORM_WATCHOS;
3829+
default:
3830+
return /*Unknown platform*/ 0;
3831+
}
3832+
}
3833+
3834+
static llvm::Value *emitIsPlatformVersionAtLeast(CodeGenFunction &CGF,
3835+
const VersionTuple &Version) {
3836+
CodeGenModule &CGM = CGF.CGM;
3837+
// Note: we intend to support multi-platform version checks, so reserve
3838+
// the room for a dual platform checking invocation that will be
3839+
// implemented in the future.
3840+
llvm::SmallVector<llvm::Value *, 8> Args;
3841+
3842+
auto EmitArgs = [&](const VersionTuple &Version, const llvm::Triple &TT) {
3843+
Optional<unsigned> Min = Version.getMinor(), SMin = Version.getSubminor();
3844+
Args.push_back(
3845+
llvm::ConstantInt::get(CGM.Int32Ty, getBaseMachOPlatformID(TT)));
3846+
Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, Version.getMajor()));
3847+
Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, Min ? *Min : 0));
3848+
Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, SMin ? *SMin : 0));
3849+
};
3850+
3851+
assert(!Version.empty() && "unexpected empty version");
3852+
EmitArgs(Version, CGM.getTarget().getTriple());
3853+
3854+
if (!CGM.IsPlatformVersionAtLeastFn) {
3855+
llvm::FunctionType *FTy = llvm::FunctionType::get(
3856+
CGM.Int32Ty, {CGM.Int32Ty, CGM.Int32Ty, CGM.Int32Ty, CGM.Int32Ty},
3857+
false);
3858+
CGM.IsPlatformVersionAtLeastFn =
3859+
CGM.CreateRuntimeFunction(FTy, "__isPlatformVersionAtLeast");
3860+
}
3861+
3862+
llvm::Value *Check =
3863+
CGF.EmitNounwindRuntimeCall(CGM.IsPlatformVersionAtLeastFn, Args);
3864+
return CGF.Builder.CreateICmpNE(Check,
3865+
llvm::Constant::getNullValue(CGM.Int32Ty));
3866+
}
3867+
38173868
llvm::Value *
3818-
CodeGenFunction::EmitBuiltinAvailable(ArrayRef<llvm::Value *> Args) {
3819-
assert(Args.size() == 3 && "Expected 3 argument here!");
3869+
CodeGenFunction::EmitBuiltinAvailable(const VersionTuple &Version) {
3870+
// Darwin uses the new __isPlatformVersionAtLeast family of routines.
3871+
if (CGM.getTarget().getTriple().isOSDarwin())
3872+
return emitIsPlatformVersionAtLeast(*this, Version);
38203873

38213874
if (!CGM.IsOSVersionAtLeastFn) {
38223875
llvm::FunctionType *FTy =
@@ -3825,18 +3878,51 @@ CodeGenFunction::EmitBuiltinAvailable(ArrayRef<llvm::Value *> Args) {
38253878
CGM.CreateRuntimeFunction(FTy, "__isOSVersionAtLeast");
38263879
}
38273880

3881+
Optional<unsigned> Min = Version.getMinor(), SMin = Version.getSubminor();
3882+
llvm::Value *Args[] = {
3883+
llvm::ConstantInt::get(CGM.Int32Ty, Version.getMajor()),
3884+
llvm::ConstantInt::get(CGM.Int32Ty, Min ? *Min : 0),
3885+
llvm::ConstantInt::get(CGM.Int32Ty, SMin ? *SMin : 0),
3886+
};
3887+
38283888
llvm::Value *CallRes =
38293889
EmitNounwindRuntimeCall(CGM.IsOSVersionAtLeastFn, Args);
38303890

38313891
return Builder.CreateICmpNE(CallRes, llvm::Constant::getNullValue(Int32Ty));
38323892
}
38333893

3894+
static bool isFoundationNeededForDarwinAvailabilityCheck(
3895+
const llvm::Triple &TT, const VersionTuple &TargetVersion) {
3896+
VersionTuple FoundationDroppedInVersion;
3897+
switch (TT.getOS()) {
3898+
case llvm::Triple::IOS:
3899+
case llvm::Triple::TvOS:
3900+
FoundationDroppedInVersion = VersionTuple(/*Major=*/13);
3901+
break;
3902+
case llvm::Triple::WatchOS:
3903+
FoundationDroppedInVersion = VersionTuple(/*Major=*/6);
3904+
break;
3905+
case llvm::Triple::Darwin:
3906+
case llvm::Triple::MacOSX:
3907+
FoundationDroppedInVersion = VersionTuple(/*Major=*/10, /*Minor=*/15);
3908+
break;
3909+
default:
3910+
llvm_unreachable("Unexpected OS");
3911+
}
3912+
return TargetVersion < FoundationDroppedInVersion;
3913+
}
3914+
38343915
void CodeGenModule::emitAtAvailableLinkGuard() {
3835-
if (!IsOSVersionAtLeastFn)
3916+
if (!IsPlatformVersionAtLeastFn)
38363917
return;
38373918
// @available requires CoreFoundation only on Darwin.
38383919
if (!Target.getTriple().isOSDarwin())
38393920
return;
3921+
// @available doesn't need Foundation on macOS 10.15+, iOS/tvOS 13+, or
3922+
// watchOS 6+.
3923+
if (!isFoundationNeededForDarwinAvailabilityCheck(
3924+
Target.getTriple(), Target.getPlatformMinVersion()))
3925+
return;
38403926
// Add -framework CoreFoundation to the linker commands. We still want to
38413927
// emit the core foundation reference down below because otherwise if
38423928
// CoreFoundation is not used in the code, the linker won't link the

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4102,7 +4102,7 @@ class CodeGenFunction : public CodeGenTypeCache {
41024102
public:
41034103
llvm::Value *EmitMSVCBuiltinExpr(MSVCIntrin BuiltinID, const CallExpr *E);
41044104

4105-
llvm::Value *EmitBuiltinAvailable(ArrayRef<llvm::Value *> Args);
4105+
llvm::Value *EmitBuiltinAvailable(const VersionTuple &Version);
41064106

41074107
llvm::Value *EmitObjCProtocolExpr(const ObjCProtocolExpr *E);
41084108
llvm::Value *EmitObjCStringLiteral(const ObjCStringLiteral *E);

clang/lib/CodeGen/CodeGenModule.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,9 +606,11 @@ class CodeGenModule : public CodeGenTypeCache {
606606
return *ObjCData;
607607
}
608608

609-
// Version checking function, used to implement ObjC's @available:
609+
// Version checking functions, used to implement ObjC's @available:
610610
// i32 @__isOSVersionAtLeast(i32, i32, i32)
611611
llvm::FunctionCallee IsOSVersionAtLeastFn = nullptr;
612+
// i32 @__isPlatformVersionAtLeast(i32, i32, i32, i32)
613+
llvm::FunctionCallee IsPlatformVersionAtLeastFn = nullptr;
612614

613615
InstrProfStats &getPGOStats() { return PGOStats; }
614616
llvm::IndexedInstrProfReader *getPGOReader() const { return PGOReader.get(); }

clang/test/CodeGenObjC/availability-cf-link-guard.m

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
// 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
44
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.12 -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
55
// RUN: %clang_cc1 -triple x86_64-unknown-linux -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
6+
// 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
7+
// RUN: %clang_cc1 -triple arm64-apple-ios13.0 -DCHECK_OS="ios 14" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
8+
// RUN: %clang_cc1 -triple arm64-apple-tvos13.0 -DCHECK_OS="tvos 14" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
9+
// RUN: %clang_cc1 -triple arm64-apple-watchos6.0 -DCHECK_OS="watchos 7" -emit-llvm -o - %s | FileCheck --check-prefix=CHECK_NO_GUARD %s
10+
// RUN: %clang_cc1 -triple arm64-apple-ios12.0 -DCHECK_OS="ios 13" -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CHECK_LINK_OPT %s
11+
// RUN: %clang_cc1 -triple arm64-apple-tvos12.0 -DCHECK_OS="tvos 13" -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CHECK_LINK_OPT %s
12+
// RUN: %clang_cc1 -triple arm64-apple-watchos5.0 -DCHECK_OS="watchos 6" -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,CHECK_LINK_OPT %s
613

714
#ifdef DEF_CF
815
struct CFBundle;
@@ -13,15 +20,19 @@
1320
// CHECK_CF-NEXT: call {{.*}}@CFBundleGetVersionNumber
1421
#endif
1522

23+
#ifndef CHECK_OS
24+
#define CHECK_OS macos 10.12
25+
#endif
26+
1627
void use_at_available() {
1728
#ifdef DEF_CF
1829
CFBundleGetVersionNumber(0);
1930
#endif
2031
#ifdef USE_BUILTIN
21-
if (__builtin_available(macos 10.12, *))
32+
if (__builtin_available(CHECK_OS, *))
2233
;
2334
#else
24-
if (@available(macos 10.12, *))
35+
if (@available(CHECK_OS, *))
2536
;
2637
#endif
2738
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.11 -emit-llvm -o - %s | FileCheck %s
22

33
void use_at_available() {
4-
// CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 12, i32 0)
4+
// CHECK: call i32 @__isPlatformVersionAtLeast(i32 1, i32 10, i32 12, i32 0)
55
// CHECK-NEXT: icmp ne
66
if (__builtin_available(macos 10.12, *))
77
;
88

9-
// CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 12, i32 0)
9+
// CHECK: call i32 @__isPlatformVersionAtLeast(i32 1, i32 10, i32 12, i32 0)
1010
// CHECK-NEXT: icmp ne
1111
if (@available(macos 10.12, *))
1212
;
1313

14-
// CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 12, i32 42)
14+
// CHECK: call i32 @__isPlatformVersionAtLeast(i32 1, i32 10, i32 12, i32 42)
1515
// CHECK-NEXT: icmp ne
1616
if (__builtin_available(ios 10, macos 10.12.42, *))
1717
;
1818

19-
// CHECK-NOT: call i32 @__isOSVersionAtLeast
19+
// CHECK-NOT: call i32 @__isPlatformVersionAtLeast
2020
// CHECK: br i1 true
2121
if (__builtin_available(ios 10, *))
2222
;
2323

2424
// This check should be folded: our deployment target is 10.11.
25-
// CHECK-NOT: call i32 @__isOSVersionAtLeast
25+
// CHECK-NOT: call i32 @__isPlatformVersionAtLeast
2626
// CHECK: br i1 true
2727
if (__builtin_available(macos 10.11, *))
2828
;
2929
}
3030

31-
// CHECK: declare i32 @__isOSVersionAtLeast(i32, i32, i32)
31+
// CHECK: declare i32 @__isPlatformVersionAtLeast(i32, i32, i32, i32)

compiler-rt/lib/builtins/os_version_check.c

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@
2424
// These three variables hold the host's OS version.
2525
static int32_t GlobalMajor, GlobalMinor, GlobalSubminor;
2626
static dispatch_once_t DispatchOnceCounter;
27+
static dispatch_once_t CompatibilityDispatchOnceCounter;
28+
29+
// _availability_version_check darwin API support.
30+
typedef uint32_t dyld_platform_t;
31+
32+
typedef struct {
33+
dyld_platform_t platform;
34+
uint32_t version;
35+
} dyld_build_version_t;
36+
37+
typedef bool (*AvailabilityVersionCheckFuncTy)(uint32_t count,
38+
dyld_build_version_t versions[]);
39+
40+
static AvailabilityVersionCheckFuncTy AvailabilityVersionCheck;
2741

2842
// We can't include <CoreFoundation/CoreFoundation.h> directly from here, so
2943
// just forward declare everything that we need from it.
@@ -72,9 +86,25 @@ typedef Boolean (*CFStringGetCStringFuncTy)(CFStringRef, char *, CFIndex,
7286
CFStringEncoding);
7387
typedef void (*CFReleaseFuncTy)(CFTypeRef);
7488

75-
// Find and parse the SystemVersion.plist file.
76-
static void parseSystemVersionPList(void *Unused) {
77-
(void)Unused;
89+
static void _initializeAvailabilityCheck(bool LoadPlist) {
90+
if (AvailabilityVersionCheck && !LoadPlist) {
91+
// New API is supported and we're not being asked to load the plist,
92+
// exit early!
93+
return;
94+
}
95+
96+
// Use the new API if it's is available.
97+
AvailabilityVersionCheck = (AvailabilityVersionCheckFuncTy)dlsym(
98+
RTLD_DEFAULT, "_availability_version_check");
99+
100+
if (AvailabilityVersionCheck && !LoadPlist) {
101+
// New API is supported and we're not being asked to load the plist,
102+
// exit early!
103+
return;
104+
}
105+
// Still load the PLIST to ensure that the existing calls to
106+
// __isOSVersionAtLeast still work even with new compiler-rt and old OSes.
107+
78108
// Load CoreFoundation dynamically
79109
const void *NullAllocator = dlsym(RTLD_DEFAULT, "kCFAllocatorNull");
80110
if (!NullAllocator)
@@ -201,9 +231,24 @@ static void parseSystemVersionPList(void *Unused) {
201231
fclose(PropertyList);
202232
}
203233

234+
// Find and parse the SystemVersion.plist file.
235+
static void compatibilityInitializeAvailabilityCheck(void *Unused) {
236+
(void)Unused;
237+
_initializeAvailabilityCheck(/*LoadPlist=*/true);
238+
}
239+
240+
static void initializeAvailabilityCheck(void *Unused) {
241+
(void)Unused;
242+
_initializeAvailabilityCheck(/*LoadPlist=*/false);
243+
}
244+
245+
// This old API entry point is no longer used by Clang for Darwin. We still need
246+
// to keep it around to ensure that object files that reference it are still
247+
// usable when linked with new compiler-rt.
204248
int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
205249
// Populate the global version variables, if they haven't already.
206-
dispatch_once_f(&DispatchOnceCounter, NULL, parseSystemVersionPList);
250+
dispatch_once_f(&CompatibilityDispatchOnceCounter, NULL,
251+
compatibilityInitializeAvailabilityCheck);
207252

208253
if (Major < GlobalMajor)
209254
return 1;
@@ -216,6 +261,23 @@ int32_t __isOSVersionAtLeast(int32_t Major, int32_t Minor, int32_t Subminor) {
216261
return Subminor <= GlobalSubminor;
217262
}
218263

264+
static inline uint32_t ConstructVersion(uint32_t Major, uint32_t Minor,
265+
uint32_t Subminor) {
266+
return ((Major & 0xffff) << 16) | ((Minor & 0xff) << 8) | (Subminor & 0xff);
267+
}
268+
269+
int32_t __isPlatformVersionAtLeast(uint32_t Platform, uint32_t Major,
270+
uint32_t Minor, uint32_t Subminor) {
271+
dispatch_once_f(&DispatchOnceCounter, NULL, initializeAvailabilityCheck);
272+
273+
if (!AvailabilityVersionCheck) {
274+
return __isOSVersionAtLeast(Major, Minor, Subminor);
275+
}
276+
dyld_build_version_t Versions[] = {
277+
{Platform, ConstructVersion(Major, Minor, Subminor)}};
278+
return AvailabilityVersionCheck(1, Versions);
279+
}
280+
219281
#elif __ANDROID__
220282

221283
#include <pthread.h>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %clang %s -o %t -mmacosx-version-min=10.6 -framework CoreFoundation -DMAJOR=%macos_version_major -DMINOR=%macos_version_minor -DSUBMINOR=%macos_version_subminor
2+
// RUN: %run %t
3+
4+
typedef int int32_t;
5+
typedef unsigned int uint32_t;
6+
7+
int32_t __isPlatformVersionAtLeast(uint32_t Platform, uint32_t Major,
8+
uint32_t Minor, uint32_t Subminor);
9+
10+
#define PLATFORM_MACOS 1
11+
12+
int32_t check(uint32_t Major, uint32_t Minor, uint32_t Subminor) {
13+
int32_t Result =
14+
__isPlatformVersionAtLeast(PLATFORM_MACOS, Major, Minor, Subminor);
15+
return Result;
16+
}
17+
18+
int main() {
19+
if (!check(MAJOR, MINOR, SUBMINOR))
20+
return 1;
21+
if (check(MAJOR, MINOR, SUBMINOR + 1))
22+
return 1;
23+
if (SUBMINOR && check(MAJOR + 1, MINOR, SUBMINOR - 1))
24+
return 1;
25+
if (SUBMINOR && !check(MAJOR, MINOR, SUBMINOR - 1))
26+
return 1;
27+
if (MAJOR && !check(MAJOR - 1, MINOR + 1, SUBMINOR))
28+
return 1;
29+
30+
return 0;
31+
}

0 commit comments

Comments
 (0)