Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -300,6 +300,24 @@ return V; } + Value *VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { + VersionTuple Version = E->getVersion(); + Optional Min = Version.getMinor(), SMin = Version.getSubminor(); + + // If we're checking for a platform older than our minimum deployment + // target, we can fold the check away. + if (Version <= CGF.CGM.getTarget().getPlatformMinVersion()) + return llvm::ConstantInt::get(Builder.getInt1Ty(), 1); + + 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.EmitObjCIsOSVersionAtLeast(Args, E->getExprLoc()); + } + Value *VisitArraySubscriptExpr(ArraySubscriptExpr *E); Value *VisitShuffleVectorExpr(ShuffleVectorExpr *E); Value *VisitConvertVectorExpr(ConvertVectorExpr *E); Index: lib/CodeGen/CGObjC.cpp =================================================================== --- lib/CodeGen/CGObjC.cpp +++ lib/CodeGen/CGObjC.cpp @@ -3374,5 +3374,205 @@ return Val; } +/// \brief Generate the function clang.is_os_version_at_least and some helpers +/// which are used to implement Objective-C's @available. +llvm::Constant * +CodeGenFunction::GenerateObjCIsOSVersionAtLeast(SourceLocation CheckLoc) { + // FIXME: Support other platforms, Gestalt() is only available on macos. + if (CGM.getTarget().getTriple().getOS() != llvm::Triple::MacOSX) + CGM.Error(CheckLoc, "@available is not yet supported on this platform"); + else if (CGM.getTarget().getPlatformMinVersion() < VersionTuple(10, 6)) + CGM.Error( + CheckLoc, + "@available requires macOS version greater than or equal to 10.6"); + + CGBuilderTy::InsertPointGuard IPG{Builder}; + + // Generate global state variables. + llvm::Constant *Zero = llvm::ConstantInt::get(Int32Ty, 0); + + llvm::GlobalVariable *MajorGlob = new llvm::GlobalVariable( + CGM.getModule(), Int32Ty, false, llvm::GlobalValue::LinkOnceODRLinkage, + Zero, "clang.os_version_major"); + llvm::GlobalVariable *MinorGlob = new llvm::GlobalVariable( + CGM.getModule(), Int32Ty, false, llvm::GlobalValue::LinkOnceODRLinkage, + Zero, "clang.os_version_minor"); + llvm::GlobalVariable *SubminorGlob = new llvm::GlobalVariable( + CGM.getModule(), Int32Ty, false, llvm::GlobalValue::LinkOnceODRLinkage, + Zero, "clang.os_version_subminor"); + + llvm::FunctionType *PopulateType = + llvm::FunctionType::get(VoidTy, {VoidPtrTy}, false); + + // This function will be passed to dispatch_once_f to populate the above + // global variables with the operating system version. + llvm::Function *Populator = cast( + CGM.CreateBuiltinFunction(PopulateType, "clang.populate_os_version")); + Populator->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage); + + llvm::BasicBlock *EntryBlock = createBasicBlock("", Populator); + Builder.SetInsertPoint(EntryBlock); + + // Generate the function that will be passed to dispatch_once_f: + // + // bool clang.populate_os_version(void *) { + // Gestalt(GestaltMajorSelector, &clang_os_version_major); + // Gestalt(GestaltMinorSelector, &clang_os_version_minor); + // Gestalt(GestaltSubminorSelector, &clang_os_version_subminor); + // } + + // Declare Gestalt(). + auto *GestaltTy = llvm::FunctionType::get( + Builder.getInt16Ty(), {Int32Ty, Int32Ty->getPointerTo()}, false); + llvm::Constant *GestaltFn = CGM.CreateRuntimeFunction(GestaltTy, "Gestalt"); + + // Check for an inconsistent definition of Gestalt() in the TU. + if (!isa(GestaltFn)) + CGM.Error(CheckLoc, "conflicting definitions for Gestalt"); + + if (llvm::Function *Fn = dyn_cast(GestaltFn)) + Fn->addAttribute(0, llvm::Attribute::SExt); + + // These are selectors that are passed into Gestalt(), depending on their + // value Gestalt() will populate the output parameter with the major, minor + // or patch version, respectivly. + const int GestaltMajorSelector = 1937339185; // 'sys1' + const int GestaltMinorSelector = 1937339186; // 'sys2' + const int GestaltSubminorSelector = 1937339187; // 'sys3' + + // Actually populate the variables. + llvm::Value *GestaltArgs[2]; + GestaltArgs[0] = llvm::ConstantInt::get(Int32Ty, GestaltMajorSelector); + GestaltArgs[1] = MajorGlob; + EmitNounwindRuntimeCall(GestaltFn, GestaltArgs); + + GestaltArgs[0] = llvm::ConstantInt::get(Int32Ty, GestaltMinorSelector); + GestaltArgs[1] = MinorGlob; + EmitNounwindRuntimeCall(GestaltFn, GestaltArgs); + + GestaltArgs[0] = llvm::ConstantInt::get(Int32Ty, GestaltSubminorSelector); + GestaltArgs[1] = SubminorGlob; + EmitNounwindRuntimeCall(GestaltFn, GestaltArgs); + + Builder.CreateRetVoid(); + + // Now that we have the populator function, generate the following function: + // + // dispatch_once_t clang_os_version_check_dispatch_once_counter; + // + // bool clang_is_os_version_at_least(int maj, int min, int bld) { + // dispatch_once_f(&clang_os_version_check_dispatch_once_counter, nullptr, + // &clang_populate_os_version); + // if (maj < clang_os_version_major) return true; + // if (clang_os_version_major < maj) return false; + // if (min < clang_os_version_minor) return true; + // if (clang_os_version_minor < min) return false; + // return bld <= clang_os_version_build; + // } + + llvm::FunctionType *DispatchOnceTy = + llvm::FunctionType::get(VoidTy, {Int64Ty->getPointerTo(), VoidPtrTy, + PopulateType->getPointerTo()}, + false); + llvm::Constant *DispatchOnceFn = + CGM.CreateRuntimeFunction(DispatchOnceTy, "dispatch_once_f"); + + // Check for an inconsistent definition of dispatch_once_f in the TU. + if (!isa(DispatchOnceFn)) + CGM.Error(CheckLoc, "conflicting definitions for dispatch_once_f"); + + llvm::GlobalVariable *DispatchCounter = new llvm::GlobalVariable( + CGM.getModule(), Int64Ty, false, llvm::GlobalValue::LinkOnceODRLinkage, + llvm::ConstantInt::get(Int64Ty, 0), + "clang.os_version_check_dispatch_once_counter"); + + llvm::FunctionType *IsOSVersionAtLeastTy = llvm::FunctionType::get( + Builder.getInt1Ty(), {Int32Ty, Int32Ty, Int32Ty}, false); + llvm::Function *IsOSVersionAtLeastFn = + cast(CGM.CreateBuiltinFunction( + IsOSVersionAtLeastTy, "clang.is_os_version_at_least")); + IsOSVersionAtLeastFn->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage); + + llvm::BasicBlock *VersionCheckEntry = + createBasicBlock("entry", IsOSVersionAtLeastFn); + + Builder.SetInsertPoint(VersionCheckEntry); + EmitNounwindRuntimeCall( + DispatchOnceFn, + {DispatchCounter, llvm::ConstantPointerNull::get(Int8PtrTy), Populator}); + + // Now that the global variables are guarenteed to be populated, we generate + // the lexographical comparsion of OS versions. + + llvm::BasicBlock *MajorCmp = + createBasicBlock("major_cmp", IsOSVersionAtLeastFn); + llvm::BasicBlock *MinorCmpA = + createBasicBlock("minor_cmp_a", IsOSVersionAtLeastFn); + llvm::BasicBlock *MinorCmpB = + createBasicBlock("minor_cmp_b", IsOSVersionAtLeastFn); + llvm::BasicBlock *SubminorCmp = + createBasicBlock("sminor_cmp", IsOSVersionAtLeastFn); + llvm::BasicBlock *Return = createBasicBlock("return", IsOSVersionAtLeastFn); + + auto ArgIt = IsOSVersionAtLeastFn->arg_begin(); + llvm::Value *Major = &*ArgIt++; + llvm::Value *Minor = &*ArgIt++; + llvm::Value *Subminor = &*ArgIt; + + llvm::Value *Cmp = Builder.CreateICmpSLT( + Major, Builder.CreateAlignedLoad(MajorGlob, getIntAlign())); + Builder.CreateCondBr(Cmp, Return, MajorCmp); + + Builder.SetInsertPoint(MajorCmp); + Cmp = Builder.CreateICmpSLT( + Builder.CreateAlignedLoad(MajorGlob, getIntAlign()), Major); + Builder.CreateCondBr(Cmp, Return, MinorCmpA); + + Builder.SetInsertPoint(MinorCmpA); + Cmp = Builder.CreateICmpSLT( + Minor, Builder.CreateAlignedLoad(MinorGlob, getIntAlign())); + Builder.CreateCondBr(Cmp, Return, MinorCmpB); + + Builder.SetInsertPoint(MinorCmpB); + Cmp = Builder.CreateICmpSLT( + Builder.CreateAlignedLoad(MinorGlob, getIntAlign()), Minor); + Builder.CreateCondBr(Cmp, Return, SubminorCmp); + + Builder.SetInsertPoint(SubminorCmp); + Cmp = Builder.CreateICmpSGE( + Builder.CreateAlignedLoad(SubminorGlob, getIntAlign()), Subminor); + Builder.CreateBr(Return); + + Builder.SetInsertPoint(Return); + llvm::PHINode *Phi = Builder.CreatePHI(Builder.getInt1Ty(), 5); + + llvm::Value *True = + llvm::ConstantInt::get(Builder.getInt1Ty(), llvm::APInt(1, 1)); + llvm::Value *False = + llvm::ConstantInt::get(Builder.getInt1Ty(), llvm::APInt(1, 0)); + + Phi->addIncoming(True, VersionCheckEntry); + Phi->addIncoming(False, MajorCmp); + Phi->addIncoming(True, MinorCmpA); + Phi->addIncoming(False, MinorCmpB); + Phi->addIncoming(Cmp, SubminorCmp); + + Builder.CreateRet(Phi); + + return IsOSVersionAtLeastFn; +} + +/// \brief Emit a call to clang.is_os_version_at_least, generating the function +/// if this is the first use. +llvm::Value * +CodeGenFunction::EmitObjCIsOSVersionAtLeast(ArrayRef Args, + SourceLocation CheckLoc) { + assert(Args.size() == 3 && "Expected 3 arguments to " + "clang.is_os_version_at_least."); + if (!CGM.ObjCIsOSVersionAtLeastFn) + CGM.ObjCIsOSVersionAtLeastFn = GenerateObjCIsOSVersionAtLeast(CheckLoc); + + return EmitNounwindRuntimeCall(CGM.ObjCIsOSVersionAtLeastFn, Args); +} CGObjCRuntime::~CGObjCRuntime() {} Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -3150,6 +3150,12 @@ RValue EmitObjCMessageExpr(const ObjCMessageExpr *E, ReturnValueSlot Return = ReturnValueSlot()); +private: + llvm::Constant *GenerateObjCIsOSVersionAtLeast(SourceLocation CheckLoc); + +public: + llvm::Value *EmitObjCIsOSVersionAtLeast(ArrayRef Args, + SourceLocation CheckLoc); /// Retrieves the default cleanup kind for an ARC cleanup. /// Except under -fobjc-arc-eh, ARC cleanups are normal-only. CleanupKind getARCCleanupKind() { Index: lib/CodeGen/CodeGenModule.h =================================================================== --- lib/CodeGen/CodeGenModule.h +++ lib/CodeGen/CodeGenModule.h @@ -538,6 +538,10 @@ return *ObjCData; } + // Version checking function, used to implement ObjC's @available: + // i1 @clang.is_os_verison_at_least(i32, i32, i32) + llvm::Constant *ObjCIsOSVersionAtLeastFn = nullptr; + InstrProfStats &getPGOStats() { return PGOStats; } llvm::IndexedInstrProfReader *getPGOReader() const { return PGOReader.get(); } Index: test/CodeGenObjC/availability-check.m =================================================================== --- test/CodeGenObjC/availability-check.m +++ test/CodeGenObjC/availability-check.m @@ -0,0 +1,57 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.11 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -xc -triple x86_64-apple-macosx10.11 -emit-llvm -o - %s | FileCheck %s + +// CHECK: @clang.os_version_major = linkonce_odr global i32 0 +// CHECK: @clang.os_version_minor = linkonce_odr global i32 0 +// CHECK: @clang.os_version_subminor = linkonce_odr global i32 0 +// CHECK: @clang.os_version_check_dispatch_once_counter = linkonce_odr global i64 0 + +void use_at_available() { + if (__builtin_available(macos 10.12, *)) + ; + // CHECK: call i1 @clang.is_os_version_at_least(i32 10, i32 12, i32 0) + + // Test that we constant fold the check if our OS version is older than our + // deployment target: + if (__builtin_available(macos 10.11, *)) + ; + // CHECK-NOT: call i1 @clang.is_os_version_at_least + // CHECK: br i1 true +} + +// CHECK: define linkonce_odr void @clang.populate_os_version(i8*) { +// CHECK-NEXT: call i16 @Gestalt(i32 1937339185, i32* @clang.os_version_major) +// CHECK-NEXT: call i16 @Gestalt(i32 1937339186, i32* @clang.os_version_minor) +// CHECK-NEXT: call i16 @Gestalt(i32 1937339187, i32* @clang.os_version_subminor) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: declare signext i16 @Gestalt(i32, i32*) +// CHECK: declare void @dispatch_once_f(i64*, i8*, void (i8*)*) + +// CHECK: define linkonce_odr i1 @clang.is_os_version_at_least(i32, i32, i32) { +// CHECK: call void @dispatch_once_f(i64* @clang.os_version_check_dispatch_once_counter, i8* null, void (i8*)* @clang.populate_os_version) +// entry: +// CHECK: [[T0:%.*]] = load i32, i32* @clang.os_version_major, align 4 +// CHECK-NEXT: [[T1:%.*]] = icmp slt i32 %0, [[T0]] +// CHECK-NEXT: br i1 [[T1]], +// major_cmp: +// CHECK: [[T2:%.*]] = load i32, i32* @clang.os_version_major, align 4 +// CHECK-NEXT: [[T3:%.*]] = icmp slt i32 [[T2]], %0 +// CHECK-NEXT: br i1 [[T3]], +// minor_cmp_a: +// CHECK: [[T4:%.*]] = load i32, i32* @clang.os_version_minor, align 4 +// CHECK-NEXT: [[T5:%.*]] = icmp slt i32 %1, [[T4]] +// CHECK-NEXT: br i1 [[T5]], +// minor_cmp_b: +// CHECK: [[T6:%.*]] = load i32, i32* @clang.os_version_minor, align 4 +// CHECK-NEXT: [[T7:%.*]] = icmp slt i32 [[T6]], %1 +// CHECK-NEXT: br i1 [[T7]], +// sminor_cmp: +// CHECK: [[T8:%.*]] = load i32, i32* @clang.os_version_subminor, align 4 +// CHECK-NEXT: [[T9:%.*]] = icmp sge i32 [[T8]], %2 +// CHECK-NEXT: br label +// return: +// CHECK: [[T10:%.*]] = phi i1 +// CHECK-NEXT: ret i1 [[T10]] +// CHECK-NEXT: } Index: test/CodeGenObjC/availability-check2.m =================================================================== --- test/CodeGenObjC/availability-check2.m +++ test/CodeGenObjC/availability-check2.m @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.11 -emit-llvm -o - %s | FileCheck %s + +// Test that we behave correctly if the user already imported the libraries we +// depend on to implement @available. + +// CHECK: @clang.os_version_major = linkonce_odr global i32 0 +// CHECK: @clang.os_version_minor = linkonce_odr global i32 0 +// CHECK: @clang.os_version_subminor = linkonce_odr global i32 0 +// CHECK: @clang.os_version_check_dispatch_once_counter = linkonce_odr global i64 0 + +short Gestalt(int, int *); +void dispatch_once_f(long long *, char *, void (*)(char *)); + +void actually_emit() { + (void)Gestalt; + (void)dispatch_once_f; +} + +// CHECK: declare signext i16 @Gestalt(i32, i32*) +// CHECK: declare void @dispatch_once_f(i64*, i8*, void (i8*)*) + +void use_at_available() { + if (@available(macos 10.12, *)) + ; + // CHECK: call i1 @clang.is_os_version_at_least(i32 10, i32 12, i32 0) +} + +// CHECK-NOT: declare signext i16 @Gestalt(i32, i32*) +// CHECK-NOT: declare void @dispatch_once_f(i64*, i8*, void (i8*)*) + +// CHECK: define linkonce_odr void @clang.populate_os_version(i8*) +// CHECK: define linkonce_odr i1 @clang.is_os_version_at_least(i32, i32, i32)