From 8fd2c06ddd5f9e54c6e548a0742f6358364df93c Mon Sep 17 00:00:00 2001 From: Hugo Melder Date: Mon, 28 Oct 2024 03:52:44 -0700 Subject: [PATCH] Implement NSDate as a small object (tagged pointer) - clang/libobjc2 only (#451) * Implement GSSmallObject Class * Remove private concrete class access * Change secondary bias * NSDate: Get interval from rhs object in comparison * Add prefix to CONCRETE_CLASS_NAME macro --- ChangeLog | 17 +- Source/NSCalendarDate.m | 9 +- Source/NSDate.m | 1192 ++++++++++++++++++---------- Source/NSDatePrivate.h | 41 + Source/NSTimer.m | 7 +- Tests/base/NSFileManager/general.m | 3 +- 6 files changed, 832 insertions(+), 437 deletions(-) create mode 100644 Source/NSDatePrivate.h diff --git a/ChangeLog b/ChangeLog index 810a8a15eb..4be99f770f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,7 +3,22 @@ * Source/NSFileManager.m: Create an NSError object when we fail to copy because the destination item already exists. -2024-09-23: Hugo Melder +2024-10-10: Hugo Melder + + * Source/NSDate.m: + * Source/NSDatePrivate.h: + NSDate is now a small object in slot 6, when configuring GNUstep with the + clang/libobjc2 toolchain. This is done by compressing the binary64 fp + holding the seconds since reference date (because someone at Apple thought + it would be a good idea to represent a time interval as a fp). Note that + this assumes that IEEE 754 is used. + * Source/NSCalendarDate.m: + Remove access to the NSDate private concrete Class + and implement -timeIntervalSinceReferenceDate instead. + Previously, all methods of the concrete date class were + added to NSCalendarDate via GSObjCAddClassBehavior. + +2024-23-09: Hugo Melder * Headers/Foundation/NSThread.h: * Source/NSString.m: diff --git a/Source/NSCalendarDate.m b/Source/NSCalendarDate.m index 895248e370..5f8a52abcd 100644 --- a/Source/NSCalendarDate.m +++ b/Source/NSCalendarDate.m @@ -58,9 +58,6 @@ @interface GSTimeZone : NSObject // Help the compiler @class GSAbsTimeZone; @interface GSAbsTimeZone : NSObject // Help the compiler @end -@class NSGDate; -@interface NSGDate : NSObject // Help the compiler -@end #define DISTANT_FUTURE 63113990400.0 @@ -396,7 +393,6 @@ + (void) initialize absAbrIMP = (NSString* (*)(id,SEL,id)) [absClass instanceMethodForSelector: abrSEL]; - GSObjCAddClassBehavior(self, [NSGDate class]); [pool release]; } } @@ -463,6 +459,11 @@ + (id) dateWithYear: (NSInteger)year return AUTORELEASE(d); } +- (NSTimeInterval) timeIntervalSinceReferenceDate +{ + return _seconds_since_ref; +} + /** * Creates and returns a new NSCalendarDate object by taking the * value of the receiver and adding the interval in seconds specified. diff --git a/Source/NSDate.m b/Source/NSDate.m index 5f632f0ca3..b71bbb65fc 100644 --- a/Source/NSDate.m +++ b/Source/NSDate.m @@ -1,10 +1,11 @@ /** Implementation for NSDate for GNUStep - Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc. + Copyright (C) 2024 Free Software Foundation, Inc. Written by: Jeremy Bettis Rewritten by: Scott Christley - Date: March 1995 Modifications by: Richard Frith-Macdonald + Small Object Optimization by: Hugo Melder + Date: September 2024 This file is part of the GNUstep Base Library. @@ -39,9 +40,13 @@ #import "Foundation/NSScanner.h" #import "Foundation/NSTimeZone.h" #import "Foundation/NSUserDefaults.h" +#import "Foundation/NSHashTable.h" #import "GNUstepBase/GSObjCRuntime.h" #import "GSPrivate.h" +#import "GSPThread.h" + +#import "NSDatePrivate.h" #include @@ -54,38 +59,595 @@ #define NAN 0x7fffffffffffffff #endif -GS_DECLARE const NSTimeInterval NSTimeIntervalSince1970 = 978307200.0; +GS_DECLARE const NSTimeInterval NSTimeIntervalSince1970 = 978307200.0; + +static BOOL debug = NO; +static Class abstractClass = nil; +static Class concreteClass = nil; +static Class calendarClass = nil; + +static gs_mutex_t classLock = GS_MUTEX_INIT_STATIC; + +// Singleton instances for distantPast and distantFuture +static id _distantPast = nil; +static id _distantFuture = nil; + +/** + * Compression of IEEE 754 double-precision floating-point numbers + * + * libobjc2 just like Apple's Objective-C runtime implement small + * object classes, or tagged pointers in the case of Apple's runtime, + * to store a 60-bit payload and 4-bit metadata in a 64-bit pointer. + * This avoids constructing a full object on the heap. + * + * NSDate stores the time as a double-precision floating-point number + * representing the number of seconds since the reference date, the + * Cocoa epoch (2001-01-01 00:00:00 UTC). This is a 64-bit value. + * This poses a problem for small object classes, as the time value + * is too large to fit in the 60-bit payload. + * + * To solve this problem, we look at the range of values that we + * need to acurately represent. Idealy, this would include dates + * before distant past and beyond distant future. + * + * After poking around with __NSTaggedDate, here is the algorithm + * for constructing its payload: + * + * Sign and mantissa are not touched. The exponent is compressed. + * Compression: + * 1. Take the 11-bit unsigned exponent and sign-extend it to a 64-bit signed integer. + * 2. Subtract a new secondary bias of 0x3EF from the exponent. + * 3. Truncate the result to a 7-bit signed integer. + * + * The order of operations is important. The biased exponent of a + * double-precision floating-point number is in range [0, 2047] (including + * special values). Sign-extending and subtracting the secondary bias results + * in a value in range [-1007, 1040]. Truncating this to a 7-bit signed integer + * further reduces the range to [-64, 63]. + * + * When unbiasing the compressed 7-bit signed exponent with 0x3EF, we + * get a biased exponent in range [943, 1070]. We have effectively shifted + * the value range in order to represent values from + * (-1)^0 * 2^(943 - 1023) * 1.048576 = 8.673617379884035e-25 + * to (-1)^0 * 2^(1070 - 1023) * 1.048576 = 147573952589676.4 + * + * This encodes all dates for a few million years beyond distantPast and + * distantFuture, except within about 1e-25 second of the reference date. + * + * So how does decompression work? + * 1. Sign extend the 7-bit signed exponent to a 64-bit signed integer. + * 2. Add the secondary bias of 0x3EF to the exponent. + * 3. Cast the result to an unsigned 11-bit integer. + * + * Note that we only use the least-significant 3-bits for the tag in + * libobjc2, contrary to Apple's runtime which uses the most-significant + * 4-bits. + * + * We'll thus use 8-bits for the exponent. + */ + +#if USE_SMALL_DATE + +// 1-5 are already used by NSNumber and GSString +#define SMALL_DATE_MASK 6 +#define EXPONENT_BIAS 0x3EF + +#define GET_INTERVAL(obj) decompressTimeInterval((uintptr_t)obj) +#define SET_INTERVAL(obj, interval) (obj = (id)(compressTimeInterval(interval) | SMALL_DATE_MASK)) + +#define IS_CONCRETE_CLASS(obj) isSmallDate(obj) + +#define CREATE_SMALL_DATE(interval) (id)(compressTimeInterval(interval) | SMALL_DATE_MASK) + +union CompressedDouble { + uintptr_t data; + struct { + uintptr_t tag : 3; // placeholder for tag bits + uintptr_t fraction : 52; + intptr_t exponent : 8; // signed! + uintptr_t sign : 1; + }; +}; + +union DoubleBits { + double val; + struct { + uintptr_t fraction : 52; + uintptr_t exponent : 11; + uintptr_t sign : 1; + }; +}; + +static __attribute__((always_inline)) uintptr_t compressTimeInterval(NSTimeInterval interval) { + union CompressedDouble c; + union DoubleBits db; + intptr_t exponent; + + db.val = interval; + c.fraction = db.fraction; + c.sign = db.sign; + + // 1. Cast 11-bit unsigned exponent to 64-bit signed + exponent = db.exponent; + // 2. Subtract secondary Bias first + exponent -= EXPONENT_BIAS; + // 3. Truncate to 8-bit signed + c.exponent = exponent; + c.tag = 0; + + return c.data; +} + +static __attribute__((always_inline)) NSTimeInterval decompressTimeInterval(uintptr_t compressed) { + union CompressedDouble c; + union DoubleBits d; + intptr_t biased_exponent; + + c.data = compressed; + d.fraction = c.fraction; + d.sign = c.sign; + + // 1. Sign Extend 8-bit to 64-bit + biased_exponent = c.exponent; + // 2. Add secondary Bias + biased_exponent += 0x3EF; + // Cast to 11-bit unsigned exponent + d.exponent = biased_exponent; + + return d.val; +} + +static __attribute__((always_inline)) BOOL isSmallDate(id obj) { + // Do a fast check if the object is also a small date. + // libobjc2 guarantees that the classes are 16-byte (word) aligned. + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-objc-pointer-introspection" + return !!((uintptr_t)obj & SMALL_DATE_MASK); + #pragma clang diagnostic pop +} + +// Populated in +[GSSmallDate load] +static BOOL useSmallDate; + + +#else +#define GET_INTERVAL(obj) ((NSGDate*)obj)->_seconds_since_ref +#define SET_INTERVAL(obj, interval) (((NSGDate*)obj)->_seconds_since_ref = interval) + +#define IS_CONCRETE_CLASS(obj) ([obj isKindOfClass: concreteClass]) + +@interface GSDateSingle : NSGDate +@end + +@interface GSDatePast : GSDateSingle +@end + +@interface GSDateFuture : GSDateSingle +@end + +#endif + +@implementation DATE_CONCRETE_CLASS_NAME + +#if USE_SMALL_DATE + ++ (void) load +{ + useSmallDate = objc_registerSmallObjectClass_np(self, SMALL_DATE_MASK); + // If this fails, someone else has already registered a small object class for this slot. + if (unlikely(useSmallDate == NO)) + { + [NSException raise: NSInternalInconsistencyException format: @"Failed to register GSSmallDate small object class"]; + } +} + +// Overwrite default memory management methods + ++ (id) alloc +{ + return (id)SMALL_DATE_MASK; +} + ++ (id) allocWithZone: (NSZone*)aZone +{ + return (id)SMALL_DATE_MASK; +} + +- (id) copy +{ + return self; +} + +- (id) copyWithZone: (NSZone*)aZone +{ + return self; +} + +- (id) retain +{ + return self; +} + +- (NSUInteger) retainCount +{ + return UINT_MAX; +} + +- (id) autorelease +{ + return self; +} + +- (oneway void) release +{ + return; +} + +// NSObject(MemoryFootprint) informal protocol + +- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude +{ + if (0 == NSHashGet(exclude, self)) + { + return 0; + } + return 8; +} + +- (NSUInteger) sizeOfContentExcluding: (NSHashTable*)exclude +{ + return 0; +} + +- (NSUInteger) sizeOfInstance +{ + return 0; +} + +#else + ++ (void) initialize +{ + if (self == [NSDate class]) + { + [self setVersion: 1]; + } +} + +#endif + +// NSDate initialization + +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + if (isnan(secs)) + { + [NSException raise: NSInvalidArgumentException + format: @"[%@-%@] interval is not a number", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + } + +#if USE_SMALL_DATE == 0 && GS_SIZEOF_VOIDP == 4 + if (secs <= DISTANT_PAST) + { + secs = DISTANT_PAST; + } + else if (secs >= DISTANT_FUTURE) + { + secs = DISTANT_FUTURE; + } +#endif + +#if USE_SMALL_DATE == 0 + _seconds_since_ref = secs; + return self; +#else + return CREATE_SMALL_DATE(secs); +#endif +} + +- (id) initWithCoder: (NSCoder*)coder +{ + double secondsSinceRef; + if ([coder allowsKeyedCoding]) + { + secondsSinceRef = [coder decodeDoubleForKey: @"NS.time"]; + } + else + { + [coder decodeValueOfObjCType: @encode(NSTimeInterval) + at: &secondsSinceRef]; + } + +#if USE_SMALL_DATE == 0 + _seconds_since_ref = secondsSinceRef; + return self; +#else + return CREATE_SMALL_DATE(secondsSinceRef); +#endif +} + +// NSDate Hashing, Comparison and Equality + +- (NSUInteger) hash +{ + #if USE_SMALL_DATE + return (NSUInteger)self; + #else + return (NSUInteger)GET_INTERVAL(self); + #endif +} + +- (NSComparisonResult) compare: (NSDate*)otherDate +{ + double selfTime = GET_INTERVAL(self); + double otherTime; + + if (otherDate == self) + { + return NSOrderedSame; + } + if (unlikely(otherDate == nil)) + { + [NSException raise: NSInvalidArgumentException + format: @"nil argument for compare:"]; + } + + if (IS_CONCRETE_CLASS(otherDate)) + { + otherTime = GET_INTERVAL(otherDate); + } else { + otherTime = [otherDate timeIntervalSinceReferenceDate]; + } + + if (selfTime > otherTime) + { + return NSOrderedDescending; + } + if (selfTime < otherTime) + { + return NSOrderedAscending; + } + return NSOrderedSame; +} + +- (BOOL) isEqual: (id)other +{ + double selfTime = GET_INTERVAL(self); + double otherTime; + + if (other == self) + { + return YES; + } + + if (IS_CONCRETE_CLASS(other)) + { + otherTime = GET_INTERVAL(other); + } else if ([other isKindOfClass: abstractClass]) + { + otherTime = [other timeIntervalSinceReferenceDate]; + } else { + return NO; + } + + return selfTime == otherTime; +} + +- (BOOL) isEqualToDate: (NSDate*)other +{ + return [self isEqual: other]; +} + +- (NSDate*) laterDate: (NSDate*)otherDate +{ + double selfTime; + double otherTime; + + if (unlikely(otherDate == nil)) + { + [NSException raise: NSInvalidArgumentException + format: @"nil argument for laterDate:"]; + } + + selfTime = GET_INTERVAL(self); + if (IS_CONCRETE_CLASS(otherDate)) + { + otherTime = GET_INTERVAL(otherDate); + } else { + otherTime = [otherDate timeIntervalSinceReferenceDate]; + } + + // If the receiver and anotherDate represent the same date, returns the receiver. + if (selfTime <= otherTime) + { + return otherDate; + } + + return self; +} + +- (NSDate*) earlierDate: (NSDate*)otherDate +{ + double selfTime; + double otherTime; + + if (unlikely(otherDate == nil)) + { + [NSException raise: NSInvalidArgumentException + format: @"nil argument for earlierDate:"]; + } + + selfTime = GET_INTERVAL(self); + if (IS_CONCRETE_CLASS(otherDate)) + { + otherTime = GET_INTERVAL(otherDate); + } else { + otherTime = [otherDate timeIntervalSinceReferenceDate]; + } + + // If the receiver and anotherDate represent the same date, returns the receiver. + if (selfTime >= otherTime) + { + return otherDate; + } + + return self; +} + +- (void) encodeWithCoder: (NSCoder*)coder +{ + double time = GET_INTERVAL(self); + if ([coder allowsKeyedCoding]) + { + [coder encodeDouble:time forKey:@"NS.time"]; + } + else + { + [coder encodeValueOfObjCType: @encode(NSTimeInterval) + at: &time]; + } +} + +// NSDate Accessors + +- (NSTimeInterval) timeIntervalSince1970 +{ + return GET_INTERVAL(self) + NSTimeIntervalSince1970; +} + +- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate +{ + double otherTime; + if (unlikely(otherDate == nil)) + { + [NSException raise: NSInvalidArgumentException + format: @"nil argument for timeIntervalSinceDate:"]; + } + + if (IS_CONCRETE_CLASS(otherDate)) + { + otherTime = GET_INTERVAL(otherDate); + } else { + otherTime = [otherDate timeIntervalSinceReferenceDate]; + } + + return GET_INTERVAL(self) - otherTime; +} + +- (NSTimeInterval) timeIntervalSinceNow +{ + return GET_INTERVAL(self) - GSPrivateTimeNow(); +} + +- (NSTimeInterval) timeIntervalSinceReferenceDate +{ + return GET_INTERVAL(self); +} + +@end + +#if USE_SMALL_DATE == 0 +/* + * This abstract class represents a date of which there can be only + * one instance. + */ +@implementation GSDateSingle + ++ (void) initialize +{ + if (self == [GSDateSingle class]) + { + [self setVersion: 1]; + GSObjCAddClassBehavior(self, [NSGDate class]); + } +} + +- (id) autorelease +{ + return self; +} + +- (oneway void) release +{ +} + +- (id) retain +{ + return self; +} + ++ (id) allocWithZone: (NSZone*)z +{ + [NSException raise: NSInternalInconsistencyException + format: @"Attempt to allocate fixed date"]; + return nil; +} + +- (id) copyWithZone: (NSZone*)z +{ + return self; +} + +- (void) dealloc +{ + [NSException raise: NSInternalInconsistencyException + format: @"Attempt to deallocate fixed date"]; + GSNOSUPERDEALLOC; +} + +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + return self; +} + +@end + +@implementation GSDatePast + ++ (id) allocWithZone: (NSZone*)z +{ + if (_distantPast == nil) + { + id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); + + _distantPast = [obj init]; + } + return _distantPast; +} + +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + SET_INTERVAL(self, DISTANT_PAST); + return self; +} + +@end - -static BOOL debug = NO; -static Class abstractClass = nil; -static Class concreteClass = nil; -static Class calendarClass = nil; +@implementation GSDateFuture -/** - * Our concrete base class - NSCalendar date must share the ivar layout. - */ -@interface NSGDate : NSDate ++ (id) allocWithZone: (NSZone*)z { -@public - NSTimeInterval _seconds_since_ref; -} -@end + if (_distantFuture == nil) + { + id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); -@interface GSDateSingle : NSGDate -@end + _distantFuture = [obj init]; + } + return _distantFuture; +} -@interface GSDatePast : GSDateSingle -@end +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + SET_INTERVAL(self, DISTANT_FUTURE); + return self; +} -@interface GSDateFuture : GSDateSingle @end -static id _distantPast = nil; -static id _distantFuture = nil; +#endif // USE_SMALL_DATE == 0 - static NSString* findInArray(NSArray *array, unsigned pos, NSString *str) { @@ -106,17 +668,10 @@ @interface GSDateFuture : GSDateSingle static inline NSTimeInterval otherTime(NSDate* other) { - Class c; - - if (other == nil) + if (unlikely(other == nil)) [NSException raise: NSInvalidArgumentException format: @"other time nil"]; - if (GSObjCIsInstance(other) == NO) - [NSException raise: NSInvalidArgumentException format: @"other time bad"]; - c = object_getClass(other); - if (c == concreteClass || c == calendarClass) - return ((NSGDate*)other)->_seconds_since_ref; - else - return [other timeIntervalSinceReferenceDate]; + + return [other timeIntervalSinceReferenceDate]; } /** @@ -135,7 +690,7 @@ + (void) initialize { [self setVersion: 1]; abstractClass = self; - concreteClass = [NSGDate class]; + concreteClass = [DATE_CONCRETE_CLASS_NAME class]; calendarClass = [NSCalendarDate class]; } } @@ -144,7 +699,11 @@ + (id) alloc { if (self == abstractClass) { + #if USE_SMALL_DATE + return [DATE_CONCRETE_CLASS_NAME alloc]; // alloc is overridden to return a small object + #else return NSAllocateObject(concreteClass, 0, NSDefaultMallocZone()); + #endif } return NSAllocateObject(self, 0, NSDefaultMallocZone()); } @@ -153,7 +712,11 @@ + (id) allocWithZone: (NSZone*)z { if (self == abstractClass) { + #if USE_SMALL_DATE + return [DATE_CONCRETE_CLASS_NAME alloc]; // alloc is overridden to return a small object + #else return NSAllocateObject(concreteClass, 0, z); + #endif } return NSAllocateObject(self, 0, z); } @@ -885,8 +1448,7 @@ + (instancetype) dateWithNaturalLanguageString: (NSString*)string } else { - return [self dateWithTimeIntervalSinceReferenceDate: - otherTime(theDate)]; + return [self dateWithTimeIntervalSinceReferenceDate: otherTime(theDate)]; } } @@ -923,7 +1485,16 @@ + (instancetype) distantPast { if (_distantPast == nil) { - _distantPast = [GSDatePast allocWithZone: 0]; + GS_MUTEX_LOCK(classLock); + if (_distantPast == nil) + { + #if USE_SMALL_DATE + _distantPast = CREATE_SMALL_DATE(DISTANT_PAST); + #else + _distantPast = [GSDatePast allocWithZone: 0]; + #endif + } + GS_MUTEX_UNLOCK(classLock); } return _distantPast; } @@ -932,7 +1503,16 @@ + (instancetype) distantFuture { if (_distantFuture == nil) { - _distantFuture = [GSDateFuture allocWithZone: 0]; + GS_MUTEX_LOCK(classLock); + if (_distantFuture == nil) + { + #if USE_SMALL_DATE + _distantFuture = CREATE_SMALL_DATE(DISTANT_FUTURE); + #else + _distantFuture = [GSDateFuture allocWithZone: 0]; + #endif + } + GS_MUTEX_UNLOCK(classLock); } return _distantFuture; } @@ -1008,270 +1588,51 @@ - (NSString*) description d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; s = [d description]; RELEASE(d); - return s; -} - -- (NSString*) descriptionWithCalendarFormat: (NSString*)format - timeZone: (NSTimeZone*)aTimeZone - locale: (NSDictionary*)l -{ - // Easiest to just have NSCalendarDate do the work for us - NSString *s; - NSCalendarDate *d = [calendarClass alloc]; - id f; - - d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; - if (!format) - { - f = [d calendarFormat]; - } - else - { - f = format; - } - if (aTimeZone) - { - [d setTimeZone: aTimeZone]; - } - s = [d descriptionWithCalendarFormat: f locale: l]; - RELEASE(d); - return s; -} - -- (NSString *) descriptionWithLocale: (id)locale -{ - // Easiest to just have NSCalendarDate do the work for us - NSString *s; - NSCalendarDate *d = [calendarClass alloc]; - - d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; - s = [d descriptionWithLocale: locale]; - RELEASE(d); - return s; -} - -- (NSDate*) earlierDate: (NSDate*)otherDate -{ - if (otherTime(self) > otherTime(otherDate)) - { - return otherDate; - } - return self; -} - -- (void) encodeWithCoder: (NSCoder*)coder -{ - NSTimeInterval interval = [self timeIntervalSinceReferenceDate]; - - if ([coder allowsKeyedCoding]) - { - [coder encodeDouble: interval forKey: @"NS.time"]; - } - [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; -} - -- (NSUInteger) hash -{ - return (NSUInteger)[self timeIntervalSinceReferenceDate]; -} - -- (instancetype) initWithCoder: (NSCoder*)coder -{ - NSTimeInterval interval; - id o; - - if ([coder allowsKeyedCoding]) - { - interval = [coder decodeDoubleForKey: @"NS.time"]; - } - else - { - [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; - } - if (interval == DISTANT_PAST) - { - o = RETAIN([abstractClass distantPast]); - } - else if (interval == DISTANT_FUTURE) - { - o = RETAIN([abstractClass distantFuture]); - } - else - { - o = [concreteClass allocWithZone: NSDefaultMallocZone()]; - o = [o initWithTimeIntervalSinceReferenceDate: interval]; - } - DESTROY(self); - return o; -} - -- (instancetype) init -{ - return [self initWithTimeIntervalSinceReferenceDate: GSPrivateTimeNow()]; -} - -- (instancetype) initWithString: (NSString*)description -{ - // Easiest to just have NSCalendarDate do the work for us - NSCalendarDate *d = [calendarClass alloc]; - - d = [d initWithString: description]; - if (nil == d) - { - DESTROY(self); - return nil; - } - else - { - self = [self initWithTimeIntervalSinceReferenceDate: otherTime(d)]; - RELEASE(d); - return self; - } -} - -- (instancetype) initWithTimeInterval: (NSTimeInterval)secsToBeAdded - sinceDate: (NSDate*)anotherDate -{ - if (anotherDate == nil) - { - NSLog(@"initWithTimeInterval:sinceDate: given nil date"); - DESTROY(self); - return nil; - } - // Get the other date's time, add the secs and init thyself - return [self initWithTimeIntervalSinceReferenceDate: - otherTime(anotherDate) + secsToBeAdded]; -} - -- (instancetype) initWithTimeIntervalSince1970: (NSTimeInterval)seconds -{ - return [self initWithTimeIntervalSinceReferenceDate: - seconds - NSTimeIntervalSince1970]; -} - -- (instancetype) initWithTimeIntervalSinceNow: (NSTimeInterval)secsToBeAdded -{ - // Get the current time, add the secs and init thyself - return [self initWithTimeIntervalSinceReferenceDate: - GSPrivateTimeNow() + secsToBeAdded]; -} - -- (instancetype) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - [self subclassResponsibility: _cmd]; - return self; -} - -- (BOOL) isEqual: (id)other -{ - if (other != nil - && [other isKindOfClass: abstractClass] - && otherTime(self) == otherTime(other)) - { - return YES; - } - return NO; -} - -- (BOOL) isEqualToDate: (NSDate*)other -{ - if (other != nil - && otherTime(self) == otherTime(other)) - { - return YES; - } - return NO; -} - -- (NSDate*) laterDate: (NSDate*)otherDate -{ - if (otherTime(self) < otherTime(otherDate)) - { - return otherDate; - } - return self; -} - -- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder -{ - if ([aCoder isByref] == NO) - { - return self; - } - return [super replacementObjectForPortCoder: aCoder]; -} - -- (NSTimeInterval) timeIntervalSince1970 -{ - return otherTime(self) + NSTimeIntervalSince1970; -} - -- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate -{ - if (nil == otherDate) - { -#ifndef NAN - return nan(""); -#else - return NAN; -#endif - } - return otherTime(self) - otherTime(otherDate); -} - -- (NSTimeInterval) timeIntervalSinceNow -{ - return otherTime(self) - GSPrivateTimeNow(); -} - -- (NSTimeInterval) timeIntervalSinceReferenceDate -{ - [self subclassResponsibility: _cmd]; - return 0; -} - -@end - -@implementation NSGDate - -+ (void) initialize -{ - if (self == [NSDate class]) - { - [self setVersion: 1]; - } + return s; } -- (NSComparisonResult) compare: (NSDate*)otherDate +- (NSString*) descriptionWithCalendarFormat: (NSString*)format + timeZone: (NSTimeZone*)aTimeZone + locale: (NSDictionary*)l { - if (otherDate == self) - { - return NSOrderedSame; - } - if (otherDate == nil) + // Easiest to just have NSCalendarDate do the work for us + NSString *s; + NSCalendarDate *d = [calendarClass alloc]; + id f; + + d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; + if (!format) { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for compare:"]; + f = [d calendarFormat]; } - if (_seconds_since_ref > otherTime(otherDate)) + else { - return NSOrderedDescending; + f = format; } - if (_seconds_since_ref < otherTime(otherDate)) + if (aTimeZone) { - return NSOrderedAscending; + [d setTimeZone: aTimeZone]; } - return NSOrderedSame; + s = [d descriptionWithCalendarFormat: f locale: l]; + RELEASE(d); + return s; +} + +- (NSString *) descriptionWithLocale: (id)locale +{ + // Easiest to just have NSCalendarDate do the work for us + NSString *s; + NSCalendarDate *d = [calendarClass alloc]; + + d = [d initWithTimeIntervalSinceReferenceDate: otherTime(self)]; + s = [d descriptionWithLocale: locale]; + RELEASE(d); + return s; } - (NSDate*) earlierDate: (NSDate*)otherDate { - if (otherDate == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for earlierDate:"]; - } - if (_seconds_since_ref > otherTime(otherDate)) + if (otherTime(self) > otherTime(otherDate)) { return otherDate; } @@ -1280,221 +1641,198 @@ - (NSDate*) earlierDate: (NSDate*)otherDate - (void) encodeWithCoder: (NSCoder*)coder { + NSTimeInterval interval = [self timeIntervalSinceReferenceDate]; + if ([coder allowsKeyedCoding]) { - [coder encodeDouble:_seconds_since_ref forKey:@"NS.time"]; - } - else - { - [coder encodeValueOfObjCType: @encode(NSTimeInterval) - at: &_seconds_since_ref]; + [coder encodeDouble: interval forKey: @"NS.time"]; } + [coder encodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; } - (NSUInteger) hash { - return (unsigned)_seconds_since_ref; + return (NSUInteger)[self timeIntervalSinceReferenceDate]; } -- (id) initWithCoder: (NSCoder*)coder +- (instancetype) initWithCoder: (NSCoder*)coder { + NSTimeInterval interval; + id o; + if ([coder allowsKeyedCoding]) { - _seconds_since_ref = [coder decodeDoubleForKey: @"NS.time"]; + interval = [coder decodeDoubleForKey: @"NS.time"]; } else { - [coder decodeValueOfObjCType: @encode(NSTimeInterval) - at: &_seconds_since_ref]; + [coder decodeValueOfObjCType: @encode(NSTimeInterval) at: &interval]; } - return self; -} - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - if (isnan(secs)) + if (interval == DISTANT_PAST) { - [NSException raise: NSInvalidArgumentException - format: @"[%@-%@] interval is not a number", - NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + o = RETAIN([abstractClass distantPast]); } - -#if GS_SIZEOF_VOIDP == 4 - if (secs <= DISTANT_PAST) + else if (interval == DISTANT_FUTURE) { - secs = DISTANT_PAST; + o = RETAIN([abstractClass distantFuture]); } - else if (secs >= DISTANT_FUTURE) + else { - secs = DISTANT_FUTURE; + o = [concreteClass allocWithZone: NSDefaultMallocZone()]; + o = [o initWithTimeIntervalSinceReferenceDate: interval]; } -#endif - _seconds_since_ref = secs; - return self; + DESTROY(self); + return o; } -- (BOOL) isEqual: (id)other +- (instancetype) init { - if (other != nil - && [other isKindOfClass: abstractClass] - && _seconds_since_ref == otherTime(other)) - { - return YES; - } - return NO; + return [self initWithTimeIntervalSinceReferenceDate: GSPrivateTimeNow()]; } -- (BOOL) isEqualToDate: (NSDate*)other +- (instancetype) initWithString: (NSString*)description { - if (other != nil - && _seconds_since_ref == otherTime(other)) - { - return YES; - } - return NO; -} + // Easiest to just have NSCalendarDate do the work for us + NSCalendarDate *d = [calendarClass alloc]; -- (NSDate*) laterDate: (NSDate*)otherDate -{ - if (otherDate == nil) + d = [d initWithString: description]; + if (nil == d) { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for laterDate:"]; + DESTROY(self); + return nil; } - if (_seconds_since_ref < otherTime(otherDate)) + else { - return otherDate; + self = [self initWithTimeIntervalSinceReferenceDate: otherTime(d)]; + RELEASE(d); + return self; } - return self; -} - -- (NSTimeInterval) timeIntervalSince1970 -{ - return _seconds_since_ref + NSTimeIntervalSince1970; } -- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate +- (instancetype) initWithTimeInterval: (NSTimeInterval)secsToBeAdded + sinceDate: (NSDate*)anotherDate { - if (otherDate == nil) + if (anotherDate == nil) { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for timeIntervalSinceDate:"]; + NSLog(@"initWithTimeInterval:sinceDate: given nil date"); + DESTROY(self); + return nil; } - return _seconds_since_ref - otherTime(otherDate); + // Get the other date's time, add the secs and init thyself + return [self initWithTimeIntervalSinceReferenceDate: otherTime(anotherDate) + secsToBeAdded]; } -- (NSTimeInterval) timeIntervalSinceNow +- (instancetype) initWithTimeIntervalSince1970: (NSTimeInterval)seconds { - return _seconds_since_ref - GSPrivateTimeNow(); + return [self initWithTimeIntervalSinceReferenceDate: + seconds - NSTimeIntervalSince1970]; } -- (NSTimeInterval) timeIntervalSinceReferenceDate +- (instancetype) initWithTimeIntervalSinceNow: (NSTimeInterval)secsToBeAdded { - return _seconds_since_ref; + // Get the current time, add the secs and init thyself + return [self initWithTimeIntervalSinceReferenceDate: + GSPrivateTimeNow() + secsToBeAdded]; } -@end +- (instancetype) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + [self subclassResponsibility: _cmd]; + return self; +} - +- (BOOL) isEqual: (id)other +{ + if (other == nil) + { + return NO; + } -/* - * This abstract class represents a date of which there can be only - * one instance. - */ -@implementation GSDateSingle + if (self == other) + { + return YES; + } -+ (void) initialize -{ - if (self == [GSDateSingle class]) + if ([other isKindOfClass: abstractClass]) { - [self setVersion: 1]; - GSObjCAddClassBehavior(self, [NSGDate class]); + double selfTime = [self timeIntervalSinceReferenceDate]; + return selfTime == otherTime(other); } -} -- (id) autorelease -{ - return self; + return NO; } -- (oneway void) release +- (BOOL) isEqualToDate: (NSDate*)other { -} + double selfTime; + double otherTime; + if (other == nil) + { + return NO; + } -- (id) retain -{ - return self; -} + selfTime = [self timeIntervalSinceReferenceDate]; + otherTime = [other timeIntervalSinceReferenceDate]; + if (selfTime == otherTime) + { + return YES; + } -+ (id) allocWithZone: (NSZone*)z -{ - [NSException raise: NSInternalInconsistencyException - format: @"Attempt to allocate fixed date"]; - return nil; + return NO; } -- (id) copyWithZone: (NSZone*)z +- (NSDate*) laterDate: (NSDate*)otherDate { + double selfTime; + if (otherDate == nil) + { + return nil; + } + + selfTime = [self timeIntervalSinceReferenceDate]; + if (selfTime < otherTime(otherDate)) + { + return otherDate; + } return self; } -- (void) dealloc +- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder { - [NSException raise: NSInternalInconsistencyException - format: @"Attempt to deallocate fixed date"]; - GSNOSUPERDEALLOC; + if ([aCoder isByref] == NO) + { + return self; + } + return [super replacementObjectForPortCoder: aCoder]; } -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +- (NSTimeInterval) timeIntervalSince1970 { - return self; + return otherTime(self) + NSTimeIntervalSince1970; } -@end - - - -@implementation GSDatePast - -+ (id) allocWithZone: (NSZone*)z +- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate { - if (_distantPast == nil) + if (nil == otherDate) { - id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); - - _distantPast = [obj init]; +#ifndef NAN + return nan(""); +#else + return NAN; +#endif } - return _distantPast; -} - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - _seconds_since_ref = DISTANT_PAST; - return self; + return [self timeIntervalSinceReferenceDate] - otherTime(otherDate); } -@end - - -@implementation GSDateFuture - -+ (id) allocWithZone: (NSZone*)z +- (NSTimeInterval) timeIntervalSinceNow { - if (_distantFuture == nil) - { - id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); - - _distantFuture = [obj init]; - } - return _distantFuture; + return [self timeIntervalSinceReferenceDate] - GSPrivateTimeNow(); } -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +- (NSTimeInterval) timeIntervalSinceReferenceDate { - _seconds_since_ref = DISTANT_FUTURE; - return self; + [self subclassResponsibility: _cmd]; + return 0; } @end - - diff --git a/Source/NSDatePrivate.h b/Source/NSDatePrivate.h new file mode 100644 index 0000000000..07075d7480 --- /dev/null +++ b/Source/NSDatePrivate.h @@ -0,0 +1,41 @@ +/** NSDate Private Interface + Copyright (C) 2024 Free Software Foundation, Inc. + + Written by: Hugo Melder + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 USA. +*/ + +#import + +#if defined(OBJC_SMALL_OBJECT_SHIFT) && (OBJC_SMALL_OBJECT_SHIFT == 3) +#define USE_SMALL_DATE 1 +#define DATE_CONCRETE_CLASS_NAME GSSmallDate +#else +#define USE_SMALL_DATE 0 +#define DATE_CONCRETE_CLASS_NAME NSGDate +#endif + +@interface DATE_CONCRETE_CLASS_NAME : NSDate +#if USE_SMALL_DATE == 0 +{ +@public + NSTimeInterval _seconds_since_ref; +} +#endif +@end \ No newline at end of file diff --git a/Source/NSTimer.m b/Source/NSTimer.m index 1444f46c23..1646c124ca 100644 --- a/Source/NSTimer.m +++ b/Source/NSTimer.m @@ -35,9 +35,8 @@ #import "Foundation/NSRunLoop.h" #import "Foundation/NSInvocation.h" -@class NSGDate; -@interface NSGDate : NSObject // Help the compiler -@end +#import "NSDatePrivate.h" + static Class NSDate_class; /** @@ -58,7 +57,7 @@ + (void) initialize { if (self == [NSTimer class]) { - NSDate_class = [NSGDate class]; + NSDate_class = [DATE_CONCRETE_CLASS_NAME class]; } } diff --git a/Tests/base/NSFileManager/general.m b/Tests/base/NSFileManager/general.m index 6acebd3100..fb2ca6ee43 100644 --- a/Tests/base/NSFileManager/general.m +++ b/Tests/base/NSFileManager/general.m @@ -12,7 +12,7 @@ #ifdef EQ #undef EQ #endif -#define EPSILON (FLT_EPSILON*100) +#define EPSILON (DBL_EPSILON*100) #define EQ(x,y) ((x >= y - EPSILON) && (x <= y + EPSILON)) @interface MyHandler : NSObject @@ -204,6 +204,7 @@ int main() NSTimeInterval ot, nt; ot = [[oa fileCreationDate] timeIntervalSinceReferenceDate]; nt = [[na fileCreationDate] timeIntervalSinceReferenceDate]; + NSLog(@"ot = %f, nt = %f", ot, nt); PASS(EQ(ot, nt), "copy creation date equals original") ot = [[oa fileModificationDate] timeIntervalSinceReferenceDate]; nt = [[na fileModificationDate] timeIntervalSinceReferenceDate];