diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -529,14 +529,7 @@ if (Version <= CGF.CGM.getTarget().getPlatformMinVersion()) return llvm::ConstantInt::get(Builder.getInt1Ty(), 1); - Optional 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 --- 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 @@ 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 Args; + + auto EmitArgs = [&](const VersionTuple &Version, const llvm::Triple &TT) { + Optional 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 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 @@ CGM.CreateRuntimeFunction(FTy, "__isOSVersionAtLeast"); } + Optional 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 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -4102,7 +4102,7 @@ public: llvm::Value *EmitMSVCBuiltinExpr(MSVCIntrin BuiltinID, const CallExpr *E); - llvm::Value *EmitBuiltinAvailable(ArrayRef 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 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -606,9 +606,11 @@ 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 --- 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 --- 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 --- 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 directly from here, so // just forward declare everything that we need from it. @@ -72,9 +86,25 @@ 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 @@ 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 @@ 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 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 --- /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; +}