objc_msgSend:方法的快速查找

2020-09-20  本文已影响0人  Bel李玉

Cache_t的结构和原理一文中,我们通过insert函数来分析了cache的实现原理。沿着cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)这条主线我们来探索,在insert之前发生了什么,通过查看源码我们找到了void cache_fill(Class cls, SEL sel, IMP imp, id receiver)方法,在cache_fill之前是什么操作呢?

 * objc_msgSend // 1
 * cache_getImp  // 2
 *
 * Cache writers (hold cacheUpdateLock while reading or writing; not PC-checked)
 * cache_fill         (acquires lock) // 3
 * cache_expand       (only called from cache_fill) // 4
 * cache_create       (only called from cache_expand)
 * bcopy               (only called from instrumented cache_expand)
 * flush_caches        (acquires lock)
 * cache_flush        (only called from cache_fill and flush_caches)
 * cache_collect_free (only called from cache_expand and cache_flu

运行时

代码程序按照执行时机有两种状态编译时运行时

编译时:就是编译器源代码翻译成及其能识别的代码。主要是做一些简单的翻译工作

运行时:就是将代码跑起来,已经被装载到内存中去了。
iOS 中,runtime有俩个版本,一个是Legacy版本,一个是Modern版本(现行版本)

jiegoutu.png
如上图所示,OC代码会经过llvm(编译器),将代码转化为runtime System Library能识别的方法。

消息发送初步探索

我们将如下代码通过clang转化为c++

LGPerson *person = [LGPerson alloc];
[person sayHello];
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp

上面的代码还转化为

LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));

上面C++代码可简化为

LGPerson *person = objc_msgSend(objc_getClass("LGPerson"), sel_registerName("alloc"));
objc_msgSend((id)person, sel_registerName("sayHello"));

从如上代码我们可以看出,sayHello该方法调用,转化为可objc_msgSend函数调用。那objc_msgSend是怎么来发送消息呢,我们来进行下一步探讨。

objc_msgSend

源码中关于objc_msgSend的实现是一段汇编代码

    ENTRY _objc_msgLookup
    UNWIND _objc_msgLookup, NoFrame
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LLookup_NilOrTagged //  (MSB tagged pointer looks negative)
#else
    b.eq    LLookup_Nil
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class  // 1
LLookup_GetIsaDone:
    // returns imp
    CacheLookup LOOKUP, _objc_msgLookup  // 2 

下面我们来着重分析 CacheLookup:

CacheLookup

Cache_t的结构和原理一文中,我们讲解了 cache是如何存储的,接下来,我们来分析它是如何查找的?

cacheLookup的实现如下所示:

.macro CacheLookup
LLookupStart$1:

    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
    and p12, p1, p11                // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif


    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro

其大致流程如下

总结

在这篇文章里,我们探索了探索了objc_msgSend里面的快速查找流程,主要是从cache_t缓存的方法中进行查找。

上一篇 下一篇

猜你喜欢

热点阅读