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 ClampingIntegralBool : TypeAttr { + let Spellings = [Clang<"clamping_integral_bool">]; + let Subjects = SubjectList<[TypedefName]>; + let Documentation = [ClampingIntegralBoolDocs]; +} Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -4146,6 +4146,24 @@ When compiled without ``-fobjc-arc``, this attribute is ignored. }]; } +def ClampingIntegralBoolDocs : Documentation { + let Category = DocCatType; + let Content = [{ +The ``clamping_integral_bool`` attribute appertains only to typedefs that alias +an integral type. Its meant to be used to annotate custom boolean types in +languages without a native bool type, namely (but not necessarily) in +Objective-C on macOS. When this attribute is used, stores, loads, and casts to +the boolean type become "clamped", so non-zero values become YES, and zero +values become NO. + +This attribute is just sugar on the underlying type. 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 +4212,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_clamping_integral_bool_bad_type : Error< + "clamping_integral_bool attribute must appertain to an integral 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::ClampingIntegralBool: + OS << "clamping_integral_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::EmitClampIntegerToBool(llvm::Value *Value) { + assert(Value->getType()->isIntegerTy() && "Can't clamp a non-integral type!"); + + // 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::ClampingIntegralBool)) + Value = EmitClampIntegerToBool(Value); + return Value; } @@ -1709,6 +1724,9 @@ return Builder.CreateTrunc(Value, Builder.getInt1Ty(), "tobool"); } + if (Ty->hasAttr(attr::ClampingIntegralBool)) + Value = EmitClampIntegerToBool(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::ClampingIntegralBool)) + Val = EmitClampIntegerToBool(Val); return RValue::get(Val); } Index: clang/lib/CodeGen/CGExprScalar.cpp =================================================================== --- clang/lib/CodeGen/CGExprScalar.cpp +++ clang/lib/CodeGen/CGExprScalar.cpp @@ -1212,6 +1212,11 @@ llvm::Type *DstTy = ConvertType(DstType); + if (NoncanonicalDstType->hasAttr(attr::ClampingIntegralBool)) { + 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 +2179,11 @@ assert(!DestTy->isBooleanType() && "bool should use PointerToBool"); auto *PtrExpr = Visit(E); + if (DestTy->hasAttr(attr::ClampingIntegralBool)) { + 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 integer into {0,1}. + llvm::Value *EmitClampIntegerToBool(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,20 @@ } } +static void handleClampingIntegralBoolAttr(TypeProcessingState &State, + QualType &CurType, + ParsedAttr &Attr) { + if (!CurType->isIntegralType(State.getSema().getASTContext())) { + State.getSema().Diag(Attr.getLoc(), + diag::err_clamping_integral_bool_bad_type); + Attr.setInvalid(); + return; + } + + auto *ClampAttr = createSimpleAttr( + State.getSema().getASTContext(), Attr); + CurType = State.getAttributedType(ClampAttr, CurType, CurType); +} static void processTypeAttrs(TypeProcessingState &state, QualType &type, TypeAttrLocation TAL, @@ -7664,6 +7678,11 @@ attr.setInvalid(); break; + case ParsedAttr::AT_ClampingIntegralBool: + handleClampingIntegralBoolAttr(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/CodeGen/clamping-integral-bool.m =================================================================== --- /dev/null +++ clang/test/CodeGen/clamping-integral-bool.m @@ -0,0 +1,160 @@ +// RUN: %clang_cc1 %s -triple x86_64-apple-macosx10.14 -emit-llvm -disable-llvm-passes -o - | FileCheck %s + +typedef __attribute__((clamping_integral_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]]) +} + +typedef __attribute__((clamping_integral_bool)) int int_bool; + +int_bool ib; + +void int_bool_test() { + // CHECK-LABEL: define void @int_bool_test + int *p; + + ib = 3; + // CHECK: store i32 1, i32* @ib + + ib = 0; + // CHECK: store i32 0, i32* @ib + + ib = 1; + // CHECK: store i32 1, i32* @ib + + ib = 23.f; + // CHECK: store i32 1, i32* @ib + + ib = 0.f; + // CHECK: store i32 0, i32* @ib + + int load = ib; + // CHECK: [[CMP:%.*]] = icmp ne i32 %{{.*}}, 0 + // CHECK-NEXT: zext i1 [[CMP]] to i32 +} 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 @@ -35,6 +35,7 @@ // CHECK-NEXT: Callback (SubjectMatchRule_function) // CHECK-NEXT: Capability (SubjectMatchRule_record, SubjectMatchRule_type_alias) // CHECK-NEXT: CarriesDependency (SubjectMatchRule_variable_is_parameter, SubjectMatchRule_objc_method, SubjectMatchRule_function) +// CHECK-NEXT: ClampingIntegralBool (SubjectMatchRule_type_alias) // CHECK-NEXT: Cold (SubjectMatchRule_function) // CHECK-NEXT: Common (SubjectMatchRule_variable) // CHECK-NEXT: Constructor (SubjectMatchRule_function) Index: clang/test/Sema/clamping-integral-bool.m =================================================================== --- /dev/null +++ clang/test/Sema/clamping-integral-bool.m @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +typedef __attribute__((clamping_integral_bool)) signed char BOOL; +typedef __attribute__((clamping_integral_bool)) unsigned char BOOL2; +typedef __attribute__((clamping_integral_bool)) int BOOL3; + +// expected-error@+1 {{clamping_integral_bool attribute must appertain to an integral type}} +typedef __attribute__((clamping_integral_bool)) float BOOL4; + +// expected-error@+1 {{'clamping_integral_bool' attribute takes no arguments}} +typedef __attribute__((clamping_integral_bool(1))) signed char BOOL5; + +// expected-warning@+1 {{'clamping_integral_bool' attribute only applies to typedefs}} +__attribute__((clamping_integral_bool)) signed char x;