From 7a5945034111e52dc3f531082e43af92a44740a7 Mon Sep 17 00:00:00 2001 From: rfm Date: Fri, 1 Nov 2024 08:43:15 +0000 Subject: [PATCH] Reapply "Implement NSDate as a small object (tagged pointer) - clang/libobjc2 only (#451)" because of GUI breakage: apps hang when loading NSMenu This reverts commit cac43f7bfc2f6142a80ecab595d92619472a0227. --- ChangeLog | 17 +- Source/NSCalendarDate.m | 9 +- Source/NSDate.m | 948 +++++++++++++++++++---------- Source/NSDatePrivate.h | 41 ++ Source/NSTimer.m | 7 +- Tests/base/NSFileManager/general.m | 3 +- 6 files changed, 710 insertions(+), 315 deletions(-) create mode 100644 Source/NSDatePrivate.h diff --git a/ChangeLog b/ChangeLog index a0a6c1902..7ec6b9fac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,7 +15,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 895248e37..5f8a52abc 100644 --- a/Source/NSCalendarDate.m +++ b/Source/NSCalendarDate.m @@ -58,9 +58,6 @@ @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 @@ GSPrivateTimeNow(void) absAbrIMP = (NSString* (*)(id,SEL,id)) [absClass instanceMethodForSelector: abrSEL]; - GSObjCAddClassBehavior(self, [NSGDate class]); [pool release]; } } @@ -463,6 +459,11 @@ GSPrivateTimeNow(void) 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 5f632f0ca..b71bbb65f 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 @@ -56,22 +61,160 @@ 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; + /** - * Our concrete base class - NSCalendar date must share the ivar layout. + * 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. */ -@interface NSGDate : NSDate -{ -@public - NSTimeInterval _seconds_since_ref; + +#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; } -@end + +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 @@ -82,10 +225,429 @@ static Class calendarClass = nil; @interface GSDateFuture : GSDateSingle @end -static id _distantPast = nil; -static id _distantFuture = nil; +#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 + + +@implementation GSDateFuture + ++ (id) allocWithZone: (NSZone*)z +{ + if (_distantFuture == nil) + { + id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); + + _distantFuture = [obj init]; + } + return _distantFuture; +} + +- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs +{ + SET_INTERVAL(self, DISTANT_FUTURE); + return self; +} + +@end + +#endif // USE_SMALL_DATE == 0 - static NSString* findInArray(NSArray *array, unsigned pos, NSString *str) { @@ -106,17 +668,10 @@ findInArray(NSArray *array, unsigned pos, NSString *str) 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 @@ otherTime(NSDate* other) { [self setVersion: 1]; abstractClass = self; - concreteClass = [NSGDate class]; + concreteClass = [DATE_CONCRETE_CLASS_NAME class]; calendarClass = [NSCalendarDate class]; } } @@ -144,7 +699,11 @@ otherTime(NSDate* other) { 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 @@ otherTime(NSDate* other) { 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 @@ otherTime(NSDate* other) } else { - return [self dateWithTimeIntervalSinceReferenceDate: - otherTime(theDate)]; + return [self dateWithTimeIntervalSinceReferenceDate: otherTime(theDate)]; } } @@ -923,7 +1485,16 @@ otherTime(NSDate* other) { 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 @@ otherTime(NSDate* other) { 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; } @@ -1139,8 +1719,7 @@ otherTime(NSDate* other) return nil; } // Get the other date's time, add the secs and init thyself - return [self initWithTimeIntervalSinceReferenceDate: - otherTime(anotherDate) + secsToBeAdded]; + return [self initWithTimeIntervalSinceReferenceDate: otherTime(anotherDate) + secsToBeAdded]; } - (instancetype) initWithTimeIntervalSince1970: (NSTimeInterval)seconds @@ -1164,28 +1743,54 @@ otherTime(NSDate* other) - (BOOL) isEqual: (id)other { - if (other != nil - && [other isKindOfClass: abstractClass] - && otherTime(self) == otherTime(other)) + if (other == nil) + { + return NO; + } + + if (self == other) { return YES; } + + if ([other isKindOfClass: abstractClass]) + { + double selfTime = [self timeIntervalSinceReferenceDate]; + return selfTime == otherTime(other); + } + return NO; } - (BOOL) isEqualToDate: (NSDate*)other { - if (other != nil - && otherTime(self) == otherTime(other)) + double selfTime; + double otherTime; + if (other == nil) + { + return NO; + } + + selfTime = [self timeIntervalSinceReferenceDate]; + otherTime = [other timeIntervalSinceReferenceDate]; + if (selfTime == otherTime) { return YES; } + return NO; } - (NSDate*) laterDate: (NSDate*)otherDate { - if (otherTime(self) < otherTime(otherDate)) + double selfTime; + if (otherDate == nil) + { + return nil; + } + + selfTime = [self timeIntervalSinceReferenceDate]; + if (selfTime < otherTime(otherDate)) { return otherDate; } @@ -1216,12 +1821,12 @@ otherTime(NSDate* other) return NAN; #endif } - return otherTime(self) - otherTime(otherDate); + return [self timeIntervalSinceReferenceDate] - otherTime(otherDate); } - (NSTimeInterval) timeIntervalSinceNow { - return otherTime(self) - GSPrivateTimeNow(); + return [self timeIntervalSinceReferenceDate] - GSPrivateTimeNow(); } - (NSTimeInterval) timeIntervalSinceReferenceDate @@ -1231,270 +1836,3 @@ otherTime(NSDate* other) } @end - -@implementation NSGDate - -+ (void) initialize -{ - if (self == [NSDate class]) - { - [self setVersion: 1]; - } -} - -- (NSComparisonResult) compare: (NSDate*)otherDate -{ - if (otherDate == self) - { - return NSOrderedSame; - } - if (otherDate == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for compare:"]; - } - if (_seconds_since_ref > otherTime(otherDate)) - { - return NSOrderedDescending; - } - if (_seconds_since_ref < otherTime(otherDate)) - { - return NSOrderedAscending; - } - return NSOrderedSame; -} - -- (NSDate*) earlierDate: (NSDate*)otherDate -{ - if (otherDate == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for earlierDate:"]; - } - if (_seconds_since_ref > otherTime(otherDate)) - { - return otherDate; - } - return self; -} - -- (void) encodeWithCoder: (NSCoder*)coder -{ - if ([coder allowsKeyedCoding]) - { - [coder encodeDouble:_seconds_since_ref forKey:@"NS.time"]; - } - else - { - [coder encodeValueOfObjCType: @encode(NSTimeInterval) - at: &_seconds_since_ref]; - } -} - -- (NSUInteger) hash -{ - return (unsigned)_seconds_since_ref; -} - -- (id) initWithCoder: (NSCoder*)coder -{ - if ([coder allowsKeyedCoding]) - { - _seconds_since_ref = [coder decodeDoubleForKey: @"NS.time"]; - } - else - { - [coder decodeValueOfObjCType: @encode(NSTimeInterval) - at: &_seconds_since_ref]; - } - return self; -} - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - if (isnan(secs)) - { - [NSException raise: NSInvalidArgumentException - format: @"[%@-%@] interval is not a number", - NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; - } - -#if GS_SIZEOF_VOIDP == 4 - if (secs <= DISTANT_PAST) - { - secs = DISTANT_PAST; - } - else if (secs >= DISTANT_FUTURE) - { - secs = DISTANT_FUTURE; - } -#endif - _seconds_since_ref = secs; - return self; -} - -- (BOOL) isEqual: (id)other -{ - if (other != nil - && [other isKindOfClass: abstractClass] - && _seconds_since_ref == otherTime(other)) - { - return YES; - } - return NO; -} - -- (BOOL) isEqualToDate: (NSDate*)other -{ - if (other != nil - && _seconds_since_ref == otherTime(other)) - { - return YES; - } - return NO; -} - -- (NSDate*) laterDate: (NSDate*)otherDate -{ - if (otherDate == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for laterDate:"]; - } - if (_seconds_since_ref < otherTime(otherDate)) - { - return otherDate; - } - return self; -} - -- (NSTimeInterval) timeIntervalSince1970 -{ - return _seconds_since_ref + NSTimeIntervalSince1970; -} - -- (NSTimeInterval) timeIntervalSinceDate: (NSDate*)otherDate -{ - if (otherDate == nil) - { - [NSException raise: NSInvalidArgumentException - format: @"nil argument for timeIntervalSinceDate:"]; - } - return _seconds_since_ref - otherTime(otherDate); -} - -- (NSTimeInterval) timeIntervalSinceNow -{ - return _seconds_since_ref - GSPrivateTimeNow(); -} - -- (NSTimeInterval) timeIntervalSinceReferenceDate -{ - return _seconds_since_ref; -} - -@end - - - -/* - * 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 -{ - _seconds_since_ref = DISTANT_PAST; - return self; -} - -@end - - -@implementation GSDateFuture - -+ (id) allocWithZone: (NSZone*)z -{ - if (_distantFuture == nil) - { - id obj = NSAllocateObject(self, 0, NSDefaultMallocZone()); - - _distantFuture = [obj init]; - } - return _distantFuture; -} - -- (id) initWithTimeIntervalSinceReferenceDate: (NSTimeInterval)secs -{ - _seconds_since_ref = DISTANT_FUTURE; - return self; -} - -@end - - diff --git a/Source/NSDatePrivate.h b/Source/NSDatePrivate.h new file mode 100644 index 000000000..07075d748 --- /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 1444f46c2..1646c124c 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 @@ static Class NSDate_class; { 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 6acebd310..fb2ca6ee4 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];