Index: src/OFNumber.m ================================================================== --- src/OFNumber.m +++ src/OFNumber.m @@ -40,10 +40,30 @@ @interface OFNumberPlaceholder: OFNumber @end @interface OFNumberSingleton: OFNumber @end + +#ifdef OF_OBJFW_RUNTIME +enum { + TAG_CHAR, + TAG_SHORT, + TAG_INT, + TAG_LONG, + TAG_LONG_LONG, + TAG_UNSIGNED_CHAR, + TAG_UNSIGNED_SHORT, + TAG_UNSIGNED_INT, + TAG_UNSIGNED_LONG, + TAG_UNSIGNED_LONG_LONG, +}; +# define TAG_BITS 4 +# define TAG_MASK 0xF + +@interface OFTaggedPointerNumber: OFNumberSingleton +@end +#endif static struct { Class isa; } placeholder; @@ -68,10 +88,14 @@ SINGLETON(unsignedLongZeroNumber, initWithUnsignedLong:, 0) SINGLETON(unsignedLongLongZeroNumber, initWithUnsignedLongLong:, 0) SINGLETON(floatZeroNumber, initWithFloat:, 0) SINGLETON(doubleZeroNumber, initWithDouble:, 0) #undef SINGLETON + +#ifdef OF_OBJFW_RUNTIME +static int numberTag; +#endif static bool isUnsigned(OFNumber *number) { switch (*number.objCType) { @@ -136,16 +160,26 @@ of_once(&once, falseNumberInit); return (id)falseNumber; } } +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif - (instancetype)initWithChar: (signed char)value { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, charZeroNumberInit); return (id)charZeroNumber; + } else if ((unsigned char)value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)(unsigned char)value << TAG_BITS) | TAG_CHAR); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithChar: value]; } @@ -153,10 +187,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, shortZeroNumberInit); return (id)shortZeroNumber; + } else if ((unsigned short)value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)(unsigned short)value << TAG_BITS) | TAG_SHORT); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithShort: value]; } @@ -164,10 +204,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, intZeroNumberInit); return (id)intZeroNumber; + } else if ((unsigned int)value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)(unsigned int)value << TAG_BITS) | TAG_INT); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithInt: value]; } @@ -175,10 +221,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, longZeroNumberInit); return (id)longZeroNumber; + } else if ((unsigned long)value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)(unsigned long)value << TAG_BITS) | TAG_LONG); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithLong: value]; } @@ -186,10 +238,17 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, longLongZeroNumberInit); return (id)longLongZeroNumber; + } else if ((unsigned long long)value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)(unsigned long long)value << TAG_BITS) | + TAG_LONG_LONG); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithLongLong: value]; } @@ -197,10 +256,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, unsignedCharZeroNumberInit); return (id)unsignedCharZeroNumber; + } else if (value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)value << TAG_BITS) | TAG_UNSIGNED_CHAR); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithUnsignedChar: value]; } @@ -208,10 +273,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, unsignedShortZeroNumberInit); return (id)unsignedShortZeroNumber; + } else if (value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)value << TAG_BITS) | TAG_UNSIGNED_SHORT); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithUnsignedShort: value]; } @@ -219,10 +290,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, unsignedIntZeroNumberInit); return (id)unsignedIntZeroNumber; + } else if (value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)value << TAG_BITS) | TAG_UNSIGNED_INT); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithUnsignedInt: value]; } @@ -230,10 +307,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, unsignedLongZeroNumberInit); return (id)unsignedLongZeroNumber; + } else if (value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)value << TAG_BITS) | TAG_UNSIGNED_LONG); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithUnsignedLong: value]; } @@ -241,10 +324,16 @@ { if (value == 0) { static of_once_t once = OF_ONCE_INIT; of_once(&once, unsignedLongLongZeroNumberInit); return (id)unsignedLongLongZeroNumber; + } else if (value <= (UINTPTR_MAX >> TAG_BITS)) { + id ret = objc_createTaggedPointer(numberTag, + ((uintptr_t)value << TAG_BITS) | TAG_UNSIGNED_LONG_LONG); + + if (ret != nil) + return ret; } return (id)[[OFNumber of_alloc] initWithUnsignedLongLong: value]; } @@ -272,10 +361,13 @@ - (instancetype)initWithSerialization: (OFXMLElement *)element { return (id)[[OFNumber of_alloc] initWithSerialization: element]; } +#ifdef __clang__ +# pragma clang diagnostic pop +#endif @end @implementation OFNumberSingleton - (instancetype)autorelease { @@ -294,16 +386,100 @@ - (unsigned int)retainCount { return OF_RETAIN_COUNT_MAX; } @end + +#ifdef OF_OBJFW_RUNTIME +@implementation OFTaggedPointerNumber +- (const char *)objCType +{ + uintptr_t value = object_getTaggedPointerValue(self); + + switch (value & TAG_MASK) { + case TAG_CHAR: + return @encode(signed char); + case TAG_SHORT: + return @encode(short); + case TAG_INT: + return @encode(int); + case TAG_LONG: + return @encode(long); + case TAG_LONG_LONG: + return @encode(long long); + case TAG_UNSIGNED_CHAR: + return @encode(unsigned char); + case TAG_UNSIGNED_SHORT: + return @encode(unsigned short); + case TAG_UNSIGNED_INT: + return @encode(unsigned int); + case TAG_UNSIGNED_LONG: + return @encode(unsigned long); + case TAG_UNSIGNED_LONG_LONG: + return @encode(unsigned long long); + default: + @throw [OFInvalidArgumentException exception]; + } +} + +# define RETURN_VALUE \ + uintptr_t value = object_getTaggedPointerValue(self); \ + \ + switch (value & TAG_MASK) { \ + case TAG_CHAR: \ + return (signed char)(unsigned char)(value >> TAG_BITS); \ + case TAG_SHORT: \ + return (short)(unsigned short)(value >> TAG_BITS); \ + case TAG_INT: \ + return (int)(unsigned int)(value >> TAG_BITS); \ + case TAG_LONG: \ + return (long)(unsigned long)(value >> TAG_BITS); \ + case TAG_LONG_LONG: \ + return (long long)(unsigned long long)(value >> TAG_BITS); \ + case TAG_UNSIGNED_CHAR: \ + return (unsigned char)(value >> TAG_BITS); \ + case TAG_UNSIGNED_SHORT: \ + return (unsigned short)(value >> TAG_BITS); \ + case TAG_UNSIGNED_INT: \ + return (unsigned int)(value >> TAG_BITS); \ + case TAG_UNSIGNED_LONG: \ + return (unsigned long)(value >> TAG_BITS); \ + case TAG_UNSIGNED_LONG_LONG: \ + return (unsigned long long)(value >> TAG_BITS); \ + default: \ + @throw [OFInvalidArgumentException exception]; \ + } +- (long long)longLongValue +{ + RETURN_VALUE +} + +- (unsigned long long)unsignedLongLongValue +{ + RETURN_VALUE +} + +- (double)doubleValue +{ + RETURN_VALUE +} +@end +# undef RETURN_VALUE +#endif @implementation OFNumber + (void)initialize { - if (self == [OFNumber class]) - placeholder.isa = [OFNumberPlaceholder class]; + if (self != [OFNumber class]) + return; + + placeholder.isa = [OFNumberPlaceholder class]; + +#ifdef OF_OBJFW_RUNTIME + numberTag = + objc_registerTaggedPointerClass([OFTaggedPointerNumber class]); +#endif } + (instancetype)of_alloc { return [super alloc]; @@ -553,11 +729,11 @@ @try { void *pool = objc_autoreleasePoolPush(); OFString *typeString; - if (![element.name isEqual: self.className] || + if (![element.name isEqual: @"OFNumber"] || ![element.namespace isEqual: OF_SERIALIZATION_NS]) @throw [OFInvalidArgumentException exception]; typeString = [element attributeForName: @"type"].stringValue; @@ -850,11 +1026,11 @@ - (OFXMLElement *)XMLElementBySerializing { void *pool = objc_autoreleasePoolPush(); OFXMLElement *element; - element = [OFXMLElement elementWithName: self.className + element = [OFXMLElement elementWithName: @"OFNumber" namespace: OF_SERIALIZATION_NS stringValue: self.description]; if (*self.objCType == 'B') [element addAttributeWithName: @"type" Index: tests/OFNumberTests.m ================================================================== --- tests/OFNumberTests.m +++ tests/OFNumberTests.m @@ -36,9 +36,59 @@ TEST(@"-[hash]", num.hash == 0x82D8BC42) TEST(@"-[charValue]", num.charValue == 21) TEST(@"-[doubleValue]", num.doubleValue == 123456789.L) + + TEST(@"signed char minimum & maximum unmodified", + (num = [OFNumber numberWithChar: SCHAR_MIN]) && + num.charValue == SCHAR_MIN && + (num = [OFNumber numberWithChar: SCHAR_MAX]) && + num.charValue == SCHAR_MAX) + + TEST(@"short minimum & maximum unmodified", + (num = [OFNumber numberWithShort: SHRT_MIN]) && + num.shortValue == SHRT_MIN && + (num = [OFNumber numberWithShort: SHRT_MAX]) && + num.shortValue == SHRT_MAX) + + TEST(@"int minimum & maximum unmodified", + (num = [OFNumber numberWithInt: INT_MIN]) && + num.intValue == INT_MIN && + (num = [OFNumber numberWithInt: INT_MAX]) && + num.intValue == INT_MAX) + + TEST(@"long minimum & maximum unmodified", + (num = [OFNumber numberWithLong: LONG_MIN]) && + num.longValue == LONG_MIN && + (num = [OFNumber numberWithLong: LONG_MAX]) && + num.longValue == LONG_MAX) + + TEST(@"long long minimum & maximum unmodified", + (num = [OFNumber numberWithLongLong: LLONG_MIN]) && + num.longLongValue == LLONG_MIN && + (num = [OFNumber numberWithLongLong: LLONG_MAX]) && + num.longLongValue == LLONG_MAX) + + TEST(@"unsigned char maximum unmodified", + (num = [OFNumber numberWithUnsignedChar: UCHAR_MAX]) && + num.unsignedCharValue == UCHAR_MAX) + + TEST(@"unsigned short maximum unmodified", + (num = [OFNumber numberWithUnsignedShort: USHRT_MAX]) && + num.unsignedShortValue == USHRT_MAX) + + TEST(@"unsigned int maximum unmodified", + (num = [OFNumber numberWithUnsignedInt: UINT_MAX]) && + num.unsignedIntValue == UINT_MAX) + + TEST(@"unsigned long maximum unmodified", + (num = [OFNumber numberWithUnsignedLong: ULONG_MAX]) && + num.unsignedLongValue == ULONG_MAX) + + TEST(@"unsigned long long maximum unmodified", + (num = [OFNumber numberWithUnsignedLongLong: ULLONG_MAX]) && + num.unsignedLongLongValue == ULLONG_MAX) objc_autoreleasePoolPop(pool); } @end