Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSKeyValueCoding: Safe-Caching for -[NSObject valueForKey:] #445

Merged
merged 19 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions Headers/CoreFoundation/CFCGTypes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/** CFCGTypes.h - CoreFoundation header file for CG types
Copyright (C) 2024 Free Software Foundation, Inc.

Written by: Hugo Melder <hugo@algoriddim.com>
Created: October 2024

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.
*/

#ifndef _CFCGTypes_h_GNUSTEP_BASE_INCLUDE
#define _CFCGTypes_h_GNUSTEP_BASE_INCLUDE

#include <float.h>
#include <stdint.h>

#define CF_DEFINES_CG_TYPES

#if defined(__has_attribute) && __has_attribute(objc_boxable)
# define CF_BOXABLE __attribute__((objc_boxable))
#else
# define CF_BOXABLE
#endif

#if (defined(__LP64__) && __LP64__) || defined(_WIN64)
# define CGFLOAT_TYPE double
# define CGFLOAT_IS_DOUBLE 1
# define CGFLOAT_MIN DBL_MIN
# define CGFLOAT_MAX DBL_MAX
# define CGFLOAT_EPSILON DBL_EPSILON
#else
# define CGFLOAT_TYPE float
# define CGFLOAT_IS_DOUBLE 0
# define CGFLOAT_MIN FLT_MIN
# define CGFLOAT_MAX FLT_MAX
# define CGFLOAT_EPSILON FLT_EPSILON
#endif

typedef CGFLOAT_TYPE CGFloat;
#define CGFLOAT_DEFINED 1

struct
CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CF_BOXABLE CGPoint CGPoint;

struct CGSize {
CGFloat width;
CGFloat height;
};
typedef struct CF_BOXABLE CGSize CGSize;

#define CGVECTOR_DEFINED 1

struct CGVector {
CGFloat dx;
CGFloat dy;
};
typedef struct CF_BOXABLE CGVector CGVector;

struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CF_BOXABLE CGRect CGRect;

enum
{
CGRectMinXEdge = 0,
CGRectMinYEdge = 1,
CGRectMaxXEdge = 2,
CGRectMaxYEdge = 3
};

typedef struct CGAffineTransform CGAffineTransform;

struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};

#define CF_DEFINES_CGAFFINETRANSFORMCOMPONENTS

/* |------------------ CGAffineTransformComponents ----------------|
*
* | a b 0 | | sx 0 0 | | 1 0 0 | | cos(t) sin(t) 0 | | 1 0 0 |
* | c d 0 | = | 0 sy 0 | * | sh 1 0 | * |-sin(t) cos(t) 0 | * | 0 1 0 |
* | tx ty 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | tx ty 1 |
* CGAffineTransform scale shear rotation translation
*/
typedef struct CGAffineTransformComponents CGAffineTransformComponents;

struct CGAffineTransformComponents {

/* Scale factors in X and Y dimensions. Negative values indicate flipping along that axis. */
CGSize scale;

/* Shear distortion along the horizontal axis. A value of 0 means no shear. */
CGFloat horizontalShear;

/* Rotation angle in radians around the origin. Sign convention may vary
* based on the coordinate system used. */
CGFloat rotation;

/* Translation or displacement along the X and Y axes. */
CGVector translation;
};


#endif // _CFCGTypes_h_GNUSTEP_BASE_INCLUDE
25 changes: 6 additions & 19 deletions Headers/Foundation/NSGeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
#ifndef __NSGeometry_h_GNUSTEP_BASE_INCLUDE
#define __NSGeometry_h_GNUSTEP_BASE_INCLUDE
#import <GNUstepBase/GSVersionMacros.h>
#import <CoreFoundation/CFCGTypes.h>

#ifdef __OBJC__
#import <objc/objc.h>

#import <Foundation/NSString.h>
#endif

#if defined(__cplusplus)
extern "C" {
Expand Down Expand Up @@ -56,12 +58,7 @@ extern "C" {
CGFloat y;
}</example>
<p>Represents a 2-d cartesian position.</p> */
typedef struct _NSPoint NSPoint;
struct _NSPoint
{
CGFloat x;
CGFloat y;
};
typedef struct CGPoint NSPoint;

#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
/** Array of NSPoint structs. */
Expand All @@ -76,12 +73,7 @@ typedef NSPoint *NSPointPointer;
CGFloat height;
}</example>
<p>Floating point rectangle size.</p> */
typedef struct _NSSize NSSize;
struct _NSSize
{
CGFloat width;
CGFloat height;
};
typedef struct CGSize NSSize;

#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
/** Array of NSSize structs. */
Expand All @@ -97,12 +89,7 @@ typedef NSSize *NSSizePointer;
}</example>

<p>Rectangle.</p> */
typedef struct _NSRect NSRect;
struct _NSRect
{
NSPoint origin;
NSSize size;
};
typedef struct CGRect NSRect;

#if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST)
/** Array of NSRect structs. */
Expand Down
13 changes: 0 additions & 13 deletions Headers/Foundation/NSObjCRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,6 @@ typedef uintptr_t NSUInteger;
# define NSUIntegerMax UINTPTR_MAX
#endif /* !defined(NSINTEGER_DEFINED) */

#if !defined(CGFLOAT_DEFINED)
#if GS_SIZEOF_VOIDP == 8
#define CGFLOAT_IS_DBL 1
typedef double CGFloat;
#define CGFLOAT_MIN DBL_MIN
#define CGFLOAT_MAX DBL_MAX
#else
typedef float CGFloat;
#define CGFLOAT_MIN FLT_MIN
#define CGFLOAT_MAX FLT_MAX
#endif
#endif /* !defined(CGFLOAT_DEFINED) */

#define NSINTEGER_DEFINED 1
#define CGFLOAT_DEFINED 1
#ifndef NS_AUTOMATED_REFCOUNT_UNAVAILABLE
Expand Down
11 changes: 7 additions & 4 deletions Source/Additions/GSObjCRuntime.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

#import "../GSPrivate.h"
#import "../GSPThread.h"
#import "../typeEncodingHelper.h"

#include <objc/Protocol.h>

Expand Down Expand Up @@ -1317,7 +1318,8 @@ unsigned long long (*imp)(id, SEL) =
break;

case _C_STRUCT_B:
if (GSSelectorTypesMatch(@encode(NSPoint), type))
{
if (IS_CGPOINT_ENCODING(type))
{
NSPoint v;

Expand All @@ -1334,7 +1336,7 @@ unsigned long long (*imp)(id, SEL) =
}
val = [NSValue valueWithPoint: v];
}
else if (GSSelectorTypesMatch(@encode(NSRange), type))
else if (IS_NSRANGE_ENCODING(type))
{
NSRange v;

Expand All @@ -1351,7 +1353,7 @@ unsigned long long (*imp)(id, SEL) =
}
val = [NSValue valueWithRange: v];
}
else if (GSSelectorTypesMatch(@encode(NSRect), type))
else if (IS_CGRECT_ENCODING(type))
{
NSRect v;

Expand All @@ -1368,7 +1370,7 @@ unsigned long long (*imp)(id, SEL) =
}
val = [NSValue valueWithRect: v];
}
else if (GSSelectorTypesMatch(@encode(NSSize), type))
else if (IS_CGSIZE_ENCODING(type))
{
NSSize v;

Expand Down Expand Up @@ -1410,6 +1412,7 @@ unsigned long long (*imp)(id, SEL) =
}
}
break;
}

default:
#ifdef __GNUSTEP_RUNTIME__
Expand Down
13 changes: 12 additions & 1 deletion Source/GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,11 @@ NSZone.m \
externs.m \
objc-load.m

ifeq ($(OBJC_RUNTIME_LIB), ng)
BASE_MFILES += \
NSKeyValueCoding+Caching.m
endif

ifneq ($(GNUSTEP_TARGET_OS), mingw32)
ifneq ($(GNUSTEP_TARGET_OS), mingw64)
ifneq ($(GNUSTEP_TARGET_OS), windows)
Expand Down Expand Up @@ -414,6 +419,11 @@ win32-load.h \
NSCallBacks.h \
tzfile.h

# Definitions for toll-free bridging of known structures
# such as NSRect, NSPoint, or NSSize.
COREFOUNDATION_HEADERS = \
CFCGTypes.h

FOUNDATION_HEADERS = \
Foundation.h \
FoundationErrors.h \
Expand Down Expand Up @@ -586,7 +596,8 @@ NSZone.h
HEADERS_INSTALL = \
$(OBJECTIVEC2_HEADERS) \
$(GNUSTEPBASE_HEADERS) \
$(FOUNDATION_HEADERS)
$(FOUNDATION_HEADERS) \
$(COREFOUNDATION_HEADERS)

GENERATED_HFILES = \
dynamic-load.h \
Expand Down
5 changes: 5 additions & 0 deletions Source/Makefile.postamble
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ after-install::
done
endif
after-install::
$(MKDIRS) $(GNUSTEP_HEADERS)/CoreFoundation
for file in $(COREFOUNDATION_HEADERS); do \
$(INSTALL_DATA) ../Headers/CoreFoundation/$$file \
$(GNUSTEP_HEADERS)/CoreFoundation/$$file ; \
done
$(MKDIRS) $(GNUSTEP_HEADERS)/GNUstepBase
for file in $(GNUSTEPBASE_HEADERS); do \
$(INSTALL_DATA) ../Headers/GNUstepBase/$$file \
Expand Down
55 changes: 55 additions & 0 deletions Source/NSKeyValueCoding+Caching.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/** Key-Value Coding Safe Caching Support
Copyright (C) 2024 Free Software Foundation, Inc.

Written by: Hugo Melder <hugo@algoriddim.com>
Created: August 2024

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.
*/

/**
* It turns out that valueForKey: is a very expensive operation, and a major
* bottleneck for Key-Value Observing and other operations such as sorting
* an array by key.
*
* The accessor search patterns for Key-Value observing are discussed in the
* Apple Key-Value Coding Programming Guide. The return value may be
* encapuslated into an NSNumber or NSValue object, depending on the Objective-C
* type encoding of the return value. This means that once valueForKey: found an
* existing accessor, the Objective-C type encoding of the accessor is
* retrieved. We then go through a huge switch case to determine the right way
* to invoke the IMP and potentially encapsulate the return type. The resulting
* object is then returned.
* The algorithm for setValue:ForKey: is similar.
*
* We can speed this up by caching the IMP of the accessor in a hash table.
* However, without proper versioning, this quickly becomes very dangerous.
* The user might exchange implementations, or add new ones expecting the
* search pattern invariant to still hold. If we clamp onto an IMP, this
* invariant no longer holds.
*
* We will make use of libobjc2's safe caching to avoid this.
*
* Note that the caching is opaque. You will only need to redirect all
* valueForKey: calls to the function below.
*/

#import "Foundation/NSString.h"

id
valueForKeyWithCaching(id obj, NSString *aKey);
Loading
Loading