erik.pilkington created this revision.
erik.pilkington added reviewers: manmanren, dexonsmith, rjmccall, thakis.
erik.pilkington added a subscriber: cfe-commits.

This patch adds CodeGen support for `@available` on macos. This is done by 
compiling @available predicates into calls to `clang.is_os_version_at_least`. 
This function first populates 3 global variables which hold the major, minor 
and subminor with the operating system version, using core services' 
`Gestalt()` 
(https://developer.apple.com/reference/coreservices/1471624-gestalt?language=objc),
 and grand central dispatch's `dispatch_once_f` to make sure that this is only 
done once. After that, it compares the version that was passed to it with the 
host's OS version.

Unfortunately, `Gestalt()` is only available on macOS. In a follow-up patch, we 
can add support for other platforms. I think that the best way of doing that is 
to depend on Objective-C's `NSProcessInfo`. I'm not super familiar with this 
part of the compiler, and in particular I'm unsure of whether we should be 
directly calling into library functions like `Gestalt` and `dispatch_once_f`, 
so please tear this patch apart!

This patch is part of a feature that I proposed last summer here:
http://lists.llvm.org/pipermail/cfe-dev/2016-July/049851.html

Thanks,
Erik


https://reviews.llvm.org/D27827

Files:
  lib/CodeGen/CGExprScalar.cpp
  lib/CodeGen/CGObjC.cpp
  lib/CodeGen/CodeGenFunction.h
  lib/CodeGen/CodeGenModule.h
  test/CodeGenObjC/availability-check.m
  test/CodeGenObjC/availability-check2.m

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)
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: 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: 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<llvm::Value *> 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/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<llvm::Function>(
+      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<llvm::Function>(GestaltFn))
+    CGM.Error(CheckLoc, "conflicting definitions for Gestalt");
+
+  if (llvm::Function *Fn = dyn_cast<llvm::Function>(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<llvm::Function>(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<llvm::Function>(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<llvm::Value *> 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/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<unsigned> 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);
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to