erik.pilkington created this revision.
erik.pilkington added reviewers: rjmccall, aaron.ballman, steven_wu.
Herald added subscribers: dexonsmith, jkorous.
Herald added a project: clang.

This attribute can be applied to typedefs of BOOL in Objective-C. It causes 
loads, stores, and casts to BOOL to clamp into {0,1}. This is useful for us 
because a lot of code is going to be moving from a platform where BOOL is a 
typedef for a native bool type (iOS) to a platform where its a typedef for 
signed char (macOS) because of macCatalyst. We can't change the type of the 
BOOL, since its ABI.

rdar://6510042

Thanks for taking a look!
Erik


Repository:
  rC Clang

https://reviews.llvm.org/D64600

Files:
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/AttrDocs.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/AST/TypePrinter.cpp
  clang/lib/CodeGen/CGExpr.cpp
  clang/lib/CodeGen/CGExprScalar.cpp
  clang/lib/CodeGen/CodeGenFunction.h
  clang/lib/Sema/SemaType.cpp
  clang/test/CodeGenObjC/objc-clamping-bool.m
  clang/test/Misc/pragma-attribute-supported-attributes-list.test
  clang/test/SemaObjC/objc-clamping-bool.m

Index: clang/test/SemaObjC/objc-clamping-bool.m
===================================================================
--- /dev/null
+++ clang/test/SemaObjC/objc-clamping-bool.m
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 %s -verify -fsyntax-only
+
+typedef __attribute__((objc_clamping_bool)) signed char BOOL;
+
+// expected-error@+1 {{'objc_clamping_bool' attribute takes no arguments}}
+typedef __attribute__((objc_clamping_bool(1))) signed char BOOL2;
+
+// expected-warning@+1 {{'objc_clamping_bool' attribute only applies to typedefs}}
+__attribute__((objc_clamping_bool)) signed char x;
+
+// expected-error@+1 {{objc_clamping_bool must appertain to a signed char type}}
+typedef __attribute__((objc_clamping_bool)) unsigned char UNSIGNED_BOOL;
Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test
===================================================================
--- clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -98,6 +98,7 @@
 // CHECK-NEXT: ObjCBridge (SubjectMatchRule_record, SubjectMatchRule_type_alias)
 // CHECK-NEXT: ObjCBridgeMutable (SubjectMatchRule_record)
 // CHECK-NEXT: ObjCBridgeRelated (SubjectMatchRule_record)
+// CHECK-NEXT: ObjCClampingBool (SubjectMatchRule_type_alias)
 // CHECK-NEXT: ObjCClassStub (SubjectMatchRule_objc_interface)
 // CHECK-NEXT: ObjCDesignatedInitializer (SubjectMatchRule_objc_method)
 // CHECK-NEXT: ObjCException (SubjectMatchRule_objc_interface)
Index: clang/test/CodeGenObjC/objc-clamping-bool.m
===================================================================
--- /dev/null
+++ clang/test/CodeGenObjC/objc-clamping-bool.m
@@ -0,0 +1,132 @@
+// RUN: %clang_cc1 %s -triple x86_64-apple-macosx10.14 -emit-llvm -disable-llvm-passes -o - | FileCheck %s
+
+typedef __attribute__((objc_clamping_bool)) signed char BOOL;
+
+BOOL b;
+
+void store() {
+  // CHECK-LABEL: define void @store
+
+  b = 2;
+  // CHECK: store i8 1, i8* @b, align 1
+
+  b = 1;
+  // CHECK: store i8 1, i8* @b, align 1
+
+  b = -1;
+  // CHECK: store i8 1, i8* @b, align 1
+
+  b = 0;
+  // CHECK: store i8 0, i8* @b, align 1
+
+  b = 256;
+  // CHECK: store i8 1, i8* @b, align 1
+
+  int unknown_value;
+  b = unknown_value;
+
+  // CHECK: [[CMPNE:%.*]] = icmp ne i32 %{{.*}}, 0
+  // CHECK-NEXT: [[ZEXT:%.*]] = zext i1 [[CMPNE]] to i8
+}
+
+void load() {
+  // CHECK-LABEL: define void @load
+  int load = b;
+
+  // CHECK: [[LOAD:%.*]] = load i8, i8* @b, align 1
+  // CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 %0, 0
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+}
+
+void cast() {
+  // CHECK-LABEL: define void @cast
+  int i;
+  float f;
+  long double ld;
+  int* p;
+  _Bool real_bool;
+
+  (BOOL)i;
+  // CHECK: [[CMP:%.*]] = icmp ne i32 %{{.*}}, 0
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+
+  (BOOL)f;
+  // CHECK: [[CMP:%.*]] = fcmp une float %{{.*}}, 0.000000e+00
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+
+  (BOOL)ld;
+  // CHECK: [[CMP:%.*]] = fcmp une x86_fp80 %{{.*}}, 0xK00000000000000000000
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+
+  (BOOL)p;
+  // CHECK: [[CMP:%.*]] = icmp ne i32* %{{.*}}, null
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+
+  (BOOL)real_bool;
+  // CHECK: [[CMP:%.*]] = icmp ne i1 %{{.*}}, false
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+}
+
+void bits() {
+  // CHECK-LABEL: define void @bits
+  struct bit_field {
+    BOOL b : 1;
+  };
+
+  struct bit_field bf;
+
+  bf.b = 1;
+  // CHECK: [[SET_BIT:%.*]] = or i8 %{{.*}}, 1
+  // CHECK-NEXT: store i8 [[SET_BIT]]
+
+  bf.b = 2;
+  // CHECK: [[SET_BIT:%.*]] = or i8 %{{.*}}, 1
+  // CHECK-NEXT: store i8 [[SET_BIT]]
+
+  bf.b = 0;
+  // CHECK: [[ZERO_BIT:%.*]] = and i8 %{{.*}}, -2
+  // CHECK-NEXT: store i8 [[ZERO_BIT]]
+
+  bf.b = -1;
+  // CHECK: [[SET_BIT:%.*]] = or i8 %{{.*}}, 1
+  // CHECK-NEXT: store i8 [[SET_BIT]]
+
+  (int)bf.b;
+  // CHECK: shl i8 %{{.*}}, 7
+  // CHECK-NEXT: [[LOAD:%.*]] = ashr i8 %{{.*}}, 7
+  // CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[LOAD]], 0
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+  // CHECK-NOT: icmp
+
+  (BOOL)bf.b;
+  // CHECK: shl i8 %{{.*}}, 7
+  // CHECK-NEXT: [[LOAD:%.*]] = ashr i8 %{{.*}}, 7
+  // CHECK-NEXT: [[CMP:%.*]] = icmp ne i8 [[LOAD]], 0
+  // CHECK-NEXT: zext i1 [[CMP]] to i8
+  // CHECK-NOT: icmp
+}
+
+@interface HasProp
+@property BOOL b;
+@property int i;
+@end
+
+void prop(HasProp *p) {
+  // CHECK-LABEL: define void @prop
+  p.b = 43;
+  // CHECK: call void {{.*}} @objc_msgSend {{.*}}(i8* {{.*}}, i8*{{.*}}, i8 signext 1)
+  p.b = 0;
+  // CHECK: call void {{.*}} @objc_msgSend {{.*}}(i8* {{.*}}, i8*{{.*}}, i8 signext 0)
+  p.b = -1;
+  // CHECK: call void {{.*}} @objc_msgSend {{.*}}(i8* {{.*}}, i8*{{.*}}, i8 signext 1)
+  p.b = 1;
+  // CHECK: call void {{.*}} @objc_msgSend {{.*}}(i8* {{.*}}, i8*{{.*}}, i8 signext 1)
+
+  BOOL b;
+
+  p.i = b;
+  // CHECK: [[CMP:%.*]] = icmp ne i8 %{{.*}}, 0
+  // CHECK-NEXT: [[ZEXT:%.*]] = zext i1 [[CMP]] to i8
+  // CHECK-NEXT: [[SEXT:%.*]] = sext i8 [[ZEXT]] to i32
+  // CHECK: call void {{.*}} @objc_msgSend {{.*}}(i8* {{.*}}, i8*{{.*}}, i32 [[SEXT]])
+}
Index: clang/lib/Sema/SemaType.cpp
===================================================================
--- clang/lib/Sema/SemaType.cpp
+++ clang/lib/Sema/SemaType.cpp
@@ -7485,6 +7485,18 @@
   }
 }
 
+static void handleObjCClampingBoolAttr(TypeProcessingState &State,
+                                       QualType &CurType, ParsedAttr &Attr) {
+  if (!CurType->isSpecificBuiltinType(BuiltinType::SChar)) {
+    State.getSema().Diag(Attr.getLoc(), diag::err_objc_clamping_bool_bad_type);
+    Attr.setInvalid();
+    return;
+  }
+
+  ObjCClampingBoolAttr *ClampAttr = createSimpleAttr<ObjCClampingBoolAttr>(
+      State.getSema().getASTContext(), Attr);
+  CurType = State.getAttributedType(ClampAttr, CurType, CurType);
+}
 
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
                              TypeAttrLocation TAL,
@@ -7664,6 +7676,11 @@
         attr.setInvalid();
       break;
 
+    case ParsedAttr::AT_ObjCClampingBool:
+      handleObjCClampingBoolAttr(state, type, attr);
+      attr.setUsedAsTypeAttr();
+      break;
+
     case ParsedAttr::AT_NoThrow:
     // Exception Specifications aren't generally supported in C mode throughout
     // clang, so revert to attribute-based handling for C.
Index: clang/lib/CodeGen/CodeGenFunction.h
===================================================================
--- clang/lib/CodeGen/CodeGenFunction.h
+++ clang/lib/CodeGen/CodeGenFunction.h
@@ -3396,6 +3396,9 @@
                         const llvm::function_ref<RValue(RValue)> &UpdateOp,
                         bool IsVolatile);
 
+  /// Emit code to "clamp" an i8 BOOL in Objective-C to {0,1}.
+  llvm::Value *EmitObjCBoolClamp(llvm::Value *);
+
   /// EmitToMemory - Change a scalar value from its value
   /// representation to its in-memory representation.
   llvm::Value *EmitToMemory(llvm::Value *Value, QualType Ty);
Index: clang/lib/CodeGen/CGExprScalar.cpp
===================================================================
--- clang/lib/CodeGen/CGExprScalar.cpp
+++ clang/lib/CodeGen/CGExprScalar.cpp
@@ -1212,6 +1212,15 @@
 
   llvm::Type *DstTy = ConvertType(DstType);
 
+  // If we're converting a value to an ObjC BOOL thats annotated with
+  // objc_clamping_bool, then emit the same code as a native bool conversion.
+  if (NoncanonicalDstType->hasAttr(attr::ObjCClampingBool)) {
+    assert(DstType->isSpecificBuiltinType(BuiltinType::SChar) &&
+           "objc_clamping_bool only appertains to signed char!");
+    Value *ToBool = EmitConversionToBool(Src, SrcType);
+    return Builder.CreateZExt(ToBool, DstTy, "BOOL.clamp");
+  }
+
   // Cast from half through float if half isn't a native type.
   if (SrcType->isHalfType() && !CGF.getContext().getLangOpts().NativeHalfType) {
     // Cast to FP using the intrinsic if the half type itself isn't supported.
@@ -2174,6 +2183,15 @@
     assert(!DestTy->isBooleanType() && "bool should use PointerToBool");
     auto *PtrExpr = Visit(E);
 
+    // If we're converting a pointer to an ObjC BOOL thats annotated with
+    // objc_clamping_bool, then emit the same code as a native bool conversion.
+    if (DestTy->hasAttr(attr::ObjCClampingBool)) {
+      assert(DestTy->isSpecificBuiltinType(BuiltinType::SChar) &&
+             "objc_clamping_bool only appertains to signed char!");
+      llvm::Value *ToBool = EmitPointerToBoolConversion(PtrExpr, E->getType());
+      return Builder.CreateZExt(ToBool, ConvertType(DestTy), "clamped.bool");
+    }
+
     if (CGF.CGM.getCodeGenOpts().StrictVTablePointers) {
       const QualType SrcType = E->getType();
 
Index: clang/lib/CodeGen/CGExpr.cpp
===================================================================
--- clang/lib/CodeGen/CGExpr.cpp
+++ clang/lib/CodeGen/CGExpr.cpp
@@ -1687,6 +1687,18 @@
   return EmitFromMemory(Load, Ty);
 }
 
+llvm::Value *CodeGenFunction::EmitObjCBoolClamp(llvm::Value *Value) {
+  assert(Value->getType()->isIntegerTy(8) && "non-signed char ObjC BOOL?");
+
+  // Avoid double-clamping if we know that Value is already boolean.
+  if (auto *ZExt = dyn_cast<llvm::ZExtInst>(Value))
+    if (ZExt->getOperand(0)->getType()->isIntegerTy(1))
+      return Value;
+
+  llvm::Value *NonZero = Builder.CreateIsNotNull(Value);
+  return Builder.CreateZExt(NonZero, Value->getType(), "BOOL.clamp");
+}
+
 llvm::Value *CodeGenFunction::EmitToMemory(llvm::Value *Value, QualType Ty) {
   // Bool has a different representation in memory than in registers.
   if (hasBooleanRepresentation(Ty)) {
@@ -1698,6 +1710,9 @@
            "wrong value rep of bool");
   }
 
+  if (Ty->hasAttr(attr::ObjCClampingBool))
+    Value = EmitObjCBoolClamp(Value);
+
   return Value;
 }
 
@@ -1709,6 +1724,9 @@
     return Builder.CreateTrunc(Value, Builder.getInt1Ty(), "tobool");
   }
 
+  if (Ty->hasAttr(attr::ObjCClampingBool))
+    Value = EmitObjCBoolClamp(Value);
+
   return Value;
 }
 
@@ -1843,6 +1861,8 @@
   }
   Val = Builder.CreateIntCast(Val, ResLTy, Info.IsSigned, "bf.cast");
   EmitScalarRangeCheck(Val, LV.getType(), Loc);
+  if (LV.getType()->hasAttr(attr::ObjCClampingBool))
+    Val = EmitObjCBoolClamp(Val);
   return RValue::get(Val);
 }
 
Index: clang/lib/AST/TypePrinter.cpp
===================================================================
--- clang/lib/AST/TypePrinter.cpp
+++ clang/lib/AST/TypePrinter.cpp
@@ -1519,6 +1519,10 @@
     OS << "ns_returns_retained";
     break;
 
+  case attr::ObjCClampingBool:
+    OS << "objc_clamping_bool";
+    break;
+
   // FIXME: When Sema learns to form this AttributedType, avoid printing the
   // attribute again in printFunctionProtoAfter.
   case attr::AnyX86NoCfCheck: OS << "nocf_check"; break;
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2708,6 +2708,8 @@
 def err_swift_abi_parameter_wrong_type : Error<
   "'%0' parameter must have pointer%select{| to unqualified pointer}1 type; "
   "type here is %2">;
+def err_objc_clamping_bool_bad_type : Error<
+  "objc_clamping_bool must appertain to a signed char type">;
 
 def err_attribute_argument_invalid : Error<
   "%0 attribute argument is invalid: %select{max must be 0 since min is 0|"
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ clang/include/clang/Basic/AttrDocs.td
@@ -4146,6 +4146,27 @@
 When compiled without ``-fobjc-arc``, this attribute is ignored.
 }]; }
 
+def ObjCClampingBoolDocs : Documentation {
+  let Category = DocCatType;
+  let Content = [{
+The ``objc_clamping_bool`` attribute appertains only to typedefs that alias type
+'signed char'. This narrow scope means that its only useful to implement
+Objective-C BOOL.
+
+On macOS, BOOL is a typedef for signed char, but on other platforms, its a
+typedef for a native boolean type. This introduces incompatibilities when
+porting code from iOS to macOS, which happens frequently (especially with
+macCatalyst). When this attribute is used, stores, loads, and casts to BOOL
+become "clamped", so non-zero values become YES, and zero values become NO.
+
+This attribute is just sugar on the underlying type, which is still signed char.
+If used with a language feature that canonicalizes the type (such as C++
+templates or auto) the sugar is stripped away, and with it the clamping
+behaviour. For this reason, its preferable to update code to perform the
+conversion manually, like: `some_value ? YES : NO`.
+  }];
+}
+
 def MIGConventionDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
@@ -4194,4 +4215,4 @@
 not initialized on device side. It has internal linkage and is initialized by
 the initializer on host side.
   }];
-}
\ No newline at end of file
+}
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ clang/include/clang/Basic/Attr.td
@@ -3274,3 +3274,9 @@
   let Subjects = SubjectList<[NonParmVar, Function, Block, ObjCMethod]>;
   let Documentation = [ObjCExternallyRetainedDocs];
 }
+
+def ObjCClampingBool : TypeAttr {
+  let Spellings = [Clang<"objc_clamping_bool">];
+  let Subjects = SubjectList<[TypedefName]>;
+  let Documentation = [ObjCClampingBoolDocs];
+}
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D64600: [O... Erik Pilkington via Phabricator via cfe-commits

Reply via email to