From a8752cd84240f5c15bd9df221d9a4bae5ded4939 Mon Sep 17 00:00:00 2001 From: David Chisnall Date: Sat, 24 Mar 2018 12:55:00 +0000 Subject: [PATCH] Clean up method / slot lookup interfaces. --- dtable.c | 4 +- objc/hooks.h | 2 +- objc/runtime.h | 21 ++++++++- runtime.c | 4 +- sendmsg2.c | 114 +++++++++++++++++++++++++++++++++++++------------ 5 files changed, 110 insertions(+), 35 deletions(-) diff --git a/dtable.c b/dtable.c index 1717d8f..b478c4f 100644 --- a/dtable.c +++ b/dtable.c @@ -49,7 +49,7 @@ static uint32_t dtable_depth = 8; */ static Class ownerForMethod(Class cls, SEL sel) { - struct objc_slot *slot = objc_get_slot2(cls, sel); + struct objc_slot *slot = objc_get_slot2(cls, sel, NULL); if (slot == NULL) { return Nil; @@ -58,7 +58,7 @@ static Class ownerForMethod(Class cls, SEL sel) { return cls; } - if (objc_get_slot2(cls->super_class, sel) == slot) + if (objc_get_slot2(cls->super_class, sel, NULL) == slot) { return ownerForMethod(cls->super_class, sel); } diff --git a/objc/hooks.h b/objc/hooks.h index 8e1c512..fed28a1 100644 --- a/objc/hooks.h +++ b/objc/hooks.h @@ -62,7 +62,7 @@ OBJC_HOOK Class (*_objc_class_for_boxing_foreign_exception)(int64_t exceptionCla * receiver. This should return the slot to use instead, although it may throw * an exception or perform some other action. */ -extern struct objc_slot* (*_objc_selector_type_mismatch2)(Class cls, +extern IMP (*_objc_selector_type_mismatch2)(Class cls, SEL selector, struct objc_slot *result); /** * Legacy hook for when selector types do not match. This is only called diff --git a/objc/runtime.h b/objc/runtime.h index fd8cb0d..6a720c4 100644 --- a/objc/runtime.h +++ b/objc/runtime.h @@ -848,11 +848,28 @@ extern struct objc_slot_v1 *objc_get_slot(Class, SEL) OBJC_NONPORTABLE OBJC_DEPRECATED; /** - * Look up a slot, without invoking any forwarding mechanisms. + * Look up a slot, without invoking any forwarding mechanisms. The third + * parameter is used to return a pointer to the current value of the version + * counter. If this value is equal to `objc_method_cache_version` then the + * slot is safe to reuse without performing another lookup. */ -extern struct objc_slot *objc_get_slot2(Class, SEL) +extern struct objc_slot *objc_get_slot2(Class, SEL, uint64_t*) OBJC_NONPORTABLE; +/** + * Look up a slot, invoking any required forwarding mechanisms. The third + * parameter is used to return a pointer to the current value of the version + * counter. If this value is equal to `objc_method_cache_version` then the + * slot is safe to reuse without performing another lookup. + */ +extern struct objc_slot *objc_slot_lookup_version(id *receiver, SEL selector, uint64_t*) + OBJC_NONPORTABLE; + +/** + * Look up a slot, invoking any required forwarding mechanisms. + */ +extern IMP objc_msg_lookup2(id *receiver, SEL selector) OBJC_NONPORTABLE; + /** * Registers a class for small objects. Small objects are stored inside a * pointer. If the class can be registered, then this returns YES. The second diff --git a/runtime.c b/runtime.c index 3d2ac67..ec5b3e1 100644 --- a/runtime.c +++ b/runtime.c @@ -362,10 +362,10 @@ Method class_getInstanceMethod(Class aClass, SEL aSelector) if (classHasInstalledDtable(aClass)) { // Do a dtable lookup to find out which class the method comes from. - struct objc_slot *slot = objc_get_slot2(aClass, aSelector); + struct objc_slot *slot = objc_get_slot2(aClass, aSelector, NULL); if (NULL == slot) { - slot = objc_get_slot2(aClass, sel_registerName(sel_getName(aSelector))); + slot = objc_get_slot2(aClass, sel_registerName(sel_getName(aSelector)), NULL); if (NULL == slot) { return NULL; diff --git a/sendmsg2.c b/sendmsg2.c index c867fee..9f8501c 100644 --- a/sendmsg2.c +++ b/sendmsg2.c @@ -24,7 +24,7 @@ static struct objc_method nil_slot_D = { (IMP)nil_method_D, NULL, NULL }; static struct objc_method nil_slot_d = { (IMP)nil_method_d, NULL, NULL }; static struct objc_method nil_slot_f = { (IMP)nil_method_f, NULL, NULL }; -struct objc_slot* objc_slot_lookup(id *receiver, SEL selector); +static struct objc_slot* objc_slot_lookup(id *receiver, SEL selector); // Default implementations of the two new hooks. Return NULL. static id objc_proxy_lookup_null(id receiver, SEL op) { return nil; } @@ -43,7 +43,7 @@ __thread struct objc_method uncacheable_slot = { (IMP)nil_method, NULL, NULL }; __thread struct objc_slot_v1 uncacheable_slot_v1 = { Nil, Nil, 0, 0, (IMP)nil_method }; #ifndef NO_SELECTOR_MISMATCH_WARNINGS -static struct objc_slot* objc_selector_type_mismatch(Class cls, SEL +static IMP objc_selector_type_mismatch(Class cls, SEL selector, struct objc_slot *result) { fprintf(stderr, "Calling [%s %c%s] with incorrect signature. " @@ -54,22 +54,22 @@ static struct objc_slot* objc_selector_type_mismatch(Class cls, SEL sel_getType_np(((struct objc_method*)result)->selector), ((struct objc_method*)result)->types, sel_getType_np(selector)); - return result; + return result->method; } #else -static struct objc_slot* objc_selector_type_mismatch(Class cls, SEL +static IMP objc_selector_type_mismatch(Class cls, SEL selector, struct objc_slot *result) { - return result; + return result->method; } #endif -struct objc_slot *(*_objc_selector_type_mismatch2)(Class cls, SEL +IMP (*_objc_selector_type_mismatch2)(Class cls, SEL selector, struct objc_slot *result) = objc_selector_type_mismatch; struct objc_slot_v1 *(*_objc_selector_type_mismatch)(Class cls, SEL selector, struct objc_slot_v1 *result); -static struct objc_slot *call_mismatch_hook(Class cls, SEL sel, struct objc_slot *slot) +static IMP call_mismatch_hook(Class cls, SEL sel, struct objc_slot *slot) { if (_objc_selector_type_mismatch && (!_objc_selector_type_mismatch2 || @@ -80,12 +80,7 @@ static struct objc_slot *call_mismatch_hook(Class cls, SEL sel, struct objc_slot fwdslot.selector = sel; fwdslot.method = slot->method; struct objc_slot_v1 *slot_v1 = _objc_selector_type_mismatch(cls, sel, &uncacheable_slot_v1); - if (slot_v1 == &fwdslot) - { - return slot; - } - uncacheable_slot.imp = slot_v1->method; - return (struct objc_slot*)&uncacheable_slot; + return slot_v1->method; } return _objc_selector_type_mismatch2(cls, sel, slot); } @@ -94,10 +89,14 @@ static // Uncomment for debugging //__attribute__((noinline)) __attribute__((always_inline)) -struct objc_slot *objc_msg_lookup_internal(id *receiver, SEL selector) +struct objc_slot *objc_msg_lookup_internal(id *receiver, SEL selector, uint64_t *version) { -retry:; + if (version) + { + *version = objc_method_cache_version; + } Class class = classForObject((*receiver)); +retry:; struct objc_slot * result = objc_dtable_lookup(class->dtable, selector->index); if (UNLIKELY(0 == result)) { @@ -126,7 +125,12 @@ retry:; } if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector)))) { - return call_mismatch_hook(class, selector, result); + if (version) + { + *version = 0; + } + uncacheable_slot.imp = call_mismatch_hook(class, selector, result); + result = (struct objc_slot*)&uncacheable_slot; } id newReceiver = objc_proxy_lookup(*receiver, selector); // If some other library wants us to play forwarding games, try @@ -138,6 +142,10 @@ retry:; } if (0 == result) { + if (version) + { + *version = 0; + } uncacheable_slot.imp = __objc_msg_forward2(*receiver, selector); result = (struct objc_slot*)&uncacheable_slot; } @@ -150,7 +158,7 @@ PRIVATE IMP slowMsgLookup(id *receiver, SEL cmd) { // By the time we've got here, the assembly version of this function has // already done the nil checks. - return objc_msg_lookup_internal(receiver, cmd)->method; + return objc_msg_lookup_internal(receiver, cmd, NULL)->method; } PRIVATE void logInt(void *a) @@ -189,7 +197,7 @@ struct objc_slot_v1 *objc_msg_lookup_sender(id *receiver, SEL selector, id sende return &nil_slot_v1; } - struct objc_slot *slot = objc_msg_lookup_internal(receiver, selector); + struct objc_slot *slot = objc_msg_lookup_internal(receiver, selector, NULL); uncacheable_slot_v1.owner = Nil; uncacheable_slot_v1.types = sel_getType_np(((struct objc_method*)slot)->selector); uncacheable_slot_v1.selector = selector; @@ -197,7 +205,8 @@ struct objc_slot_v1 *objc_msg_lookup_sender(id *receiver, SEL selector, id sende return &uncacheable_slot_v1; } -struct objc_slot* objc_slot_lookup(id *receiver, SEL selector) + +static struct objc_slot* objc_slot_lookup(id *receiver, SEL selector) { // Returning a nil slot allows the caller to cache the lookup for nil too, // although this is not particularly useful because the nil method can be @@ -224,9 +233,49 @@ struct objc_slot* objc_slot_lookup(id *receiver, SEL selector) return (struct objc_slot*)&nil_slot; } - return objc_msg_lookup_internal(receiver, selector); + return objc_msg_lookup_internal(receiver, selector, NULL); } +struct objc_slot *objc_slot_lookup_version(id *receiver, SEL selector, uint64_t *version) +{ + // Returning a nil slot allows the caller to cache the lookup for nil too, + // although this is not particularly useful because the nil method can be + // inlined trivially. + if (UNLIKELY(*receiver == nil)) + { + if (version) + { + *version = 0; + } + // Return the correct kind of zero, depending on the type encoding. + if (selector->types) + { + const char *t = selector->types; + // Skip type qualifiers + while ('r' == *t || 'n' == *t || 'N' == *t || 'o' == *t || + 'O' == *t || 'R' == *t || 'V' == *t) + { + t++; + } + switch (selector->types[0]) + { + case 'D': return (struct objc_slot*)&nil_slot_D; + case 'd': return (struct objc_slot*)&nil_slot_d; + case 'f': return (struct objc_slot*)&nil_slot_f; + } + } + return (struct objc_slot*)&nil_slot; + } + + return objc_msg_lookup_internal(receiver, selector, version); +} + +IMP objc_msg_lookup2(id *receiver, SEL selector) +{ + return objc_slot_lookup(receiver, selector)->method; +} + + struct objc_slot *objc_slot_lookup_super2(struct objc_super *super, SEL selector) { id receiver = super->receiver; @@ -378,10 +427,14 @@ void objc_msg_profile(id receiver, IMP method, } /** - * Looks up a slot without invoking any forwarding mechanisms + * looks up a slot without invoking any forwarding mechanisms */ -struct objc_slot *objc_get_slot2(Class cls, SEL selector) +struct objc_slot *objc_get_slot2(Class cls, SEL selector, uint64_t *version) { + if (version) + { + *version = objc_method_cache_version; + } struct objc_slot * result = objc_dtable_lookup(cls->dtable, selector->index); if (0 == result) { @@ -403,11 +456,16 @@ struct objc_slot *objc_get_slot2(Class cls, SEL selector) if (!isSelRegistered(selector)) { objc_register_selector(selector); - return objc_get_slot2(cls, selector); + return objc_get_slot2(cls, selector, version); } if ((result = objc_dtable_lookup(dtable, get_untyped_idx(selector)))) { - return call_mismatch_hook(cls, selector, result); + if (version) + { + *version = 0; + } + uncacheable_slot.imp = call_mismatch_hook(cls, selector, result); + result = (struct objc_slot*)&uncacheable_slot; } } } @@ -416,7 +474,7 @@ struct objc_slot *objc_get_slot2(Class cls, SEL selector) struct objc_slot_v1 *objc_get_slot(Class cls, SEL selector) { - struct objc_slot *result = objc_get_slot2(cls, selector); + struct objc_slot *result = objc_get_slot2(cls, selector, NULL); if (result == NULL) { return NULL; @@ -437,13 +495,13 @@ BOOL class_respondsToSelector(Class cls, SEL selector) { if (0 == selector || 0 == cls) { return NO; } - return NULL != objc_get_slot2(cls, selector); + return NULL != objc_get_slot2(cls, selector, NULL); } IMP class_getMethodImplementation(Class cls, SEL name) { if ((Nil == cls) || (NULL == name)) { return (IMP)0; } - struct objc_slot * slot = objc_get_slot2(cls, name); + struct objc_slot * slot = objc_get_slot2(cls, name, NULL); return NULL != slot ? slot->method : __objc_msg_forward2(nil, name); } @@ -490,7 +548,7 @@ IMP objc_msg_lookup(id receiver, SEL selector) if (nil == receiver) { return (IMP)nil_method; } id self = receiver; - struct objc_slot * slot = objc_msg_lookup_internal(&self, selector); + struct objc_slot * slot = objc_msg_lookup_internal(&self, selector, NULL); // If the receiver is changed by the lookup mechanism then we have to fall // back to old-style forwarding. if (self != receiver)