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]; +} 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/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/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/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(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/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/CodeGenFunction.h =================================================================== --- clang/lib/CodeGen/CodeGenFunction.h +++ clang/lib/CodeGen/CodeGenFunction.h @@ -3396,6 +3396,9 @@ const llvm::function_ref &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/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( + 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/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/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/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;