美文网首页
OC底层原理-objc_msgSend流程分析上

OC底层原理-objc_msgSend流程分析上

作者: 可可先生_3083 | 来源:发表于2021-06-29 18:17 被阅读0次

前言

我们都知道OC是一门动态的语言,它的核心就是rutime机制。而消息发送objc_msgSend可谓是rutime机制的基石。下面就让我们通过源码来揭开objc_msgSend的神秘面纱。

准备工作

objc-818
TaggedPointer
LP64

源码解读

来自objc818 ps:耐心地一行行解读

objc_msgSend方法入口


    ENTRY _objc_msgSend//方法入口
    UNWIND _objc_msgSend, NoFrame
    cmp p0, #0              //p0 receiver 和 0比较
#if SUPPORT_TAGGED_POINTERS //__LP64__ 64位系统 支持taggedpointer类型
    b.le    LNilOrTagged    //判断上面的cmp小于等#0,执行标号 LNilOrTagged
#else
    b.eq    LReturnZero     //判断上面的cmp等于#0,即消息接收者 = nil,执行标号LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa  把isa存进p13寄存器
    GetClassFromIsa_p16 p13, 1, x0  // 执行标号GetClassFromIsa_p16获取class并存进p16寄存器
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // 判断上面的cmp等于#0,执行标号LReturnZero
    GetTaggedClass          // 不为0,执行标号GetTaggedClass,获取class指针
    b   LGetIsaDone         // 跳转到标号LGetIsaDone处执行
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret

    END_ENTRY _objc_msgSend

流程分析:

  1. 比较p0 和 0,p0是第一个参数receiver,为空,LReturnZero
  2. 从isa读取class,分为俩种情况(TaggedPointer,Non-Pointer)
  3. LGetIsaDone->CacheLookup

TaggedPointer读取class

ps:这里值得扩展一波,taggedPointer优化了小值对象的存储

// Look up the class for a tagged pointer in x0, placing it in x16.
.macro GetTaggedClass

    and x10, x0, #0x7       // x10 = small tag
    asr x11, x0, #55        // x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer)
    cmp x10, #7     // tag == 7?
    csel    x12, x11, x10, eq   // x12 = index in tagged pointer classes array, negative for extended tags.
                    // The extended tag array is placed immediately before the basic tag array
                    // so this looks into the right place either way. The sign extension done
                    // by the asr instruction produces the value extended_tag - 256, which produces
                    // the correct index in the extended tagged pointer classes array.

    // x16 = _objc_debug_taggedpointer_classes[x12]
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ldr x16, [x10, x12, LSL #3]

.endmacro

Non-Pointer指针读取class

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA //arm64 & !__LP64__ ps:也就是32位的数据模型
    // Indexed isa
    mov p16, \src   //src是参数p13,把isa存进p16       // optimistically set dst = src
    tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
    // isa in p16 is indexed
    adrp    x10, _objc_indexed_classes@PAGE//读取classes数组所在page到x10
    add x10, x10, _objc_indexed_classes@PAGEOFF//x10 = x10+偏移量,classes数组地址
    ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index   所属class的index
    ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array 读取class地址到p16
1:

#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
    mov p16, \src //不需要验证,读取isa到p16
.else
    // 64-bit packed isa
    ExtractISA p16, \src, \auth_address  //执行标号ExtractISA 
.endif
#else
    // 32-bit raw isa
    mov p16, \src

#endif

.endmacro

ExtractISA 核心代码 isa & isa_mask

.macro ExtractISA
   and $0, $1, #ISA_MASK  //isa & isa_mask 存入参数0也就是p16
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
   xpacd   $0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
   mov x10, $2
   movk    x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
   autda   $0, x10
#endif
.endmacro

CacheLookup

此处代码已精简(删除不同cpu架构分支,保留arm64 & LP64)

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    mov x15, x16            // 把class存进x15
LLookupStart\Function:
       /**
       第一步,获取cache
      */
    // p1 = SEL, p16 = isa
      // p11 = mask|buckets,class指针平移16字节得到cache首地址,
     //也是_bucketsAndMaybeMask首地址
    ldr p11, [x16, #CACHE]          
        /**
       第二步,获取buckets
        */
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets  = p11 & bucektMask
    tbnz    p11, #0, LLookupPreopt\Function //buckets为0,查找共享缓存
     /**
    第三步,计算index
     */
     //下面俩行是cache_hash算法 ,index = (_cmd ^ (_cmd >> 7)) & mask
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
         /**
       第四步,获取index对应的的bucket地址
        */
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((index) << (1+PTRSHIFT))
         /**
       第五步,--循环,验证是否缓存命中
        */
                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b




    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

LLookupEnd\Function:
LLookupRecover\Function:
    b   \MissLabelDynamic

cache_hit

.macro CacheHit
.if $0 == NORMAL
    TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
    mov p0, p17
    cbz p0, 9f          // don't ptrauth a nil imp
    AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
9:  ret             // return IMP
.elseif $0 == LOOKUP
    // No nil check for ptrauth: the caller would crash anyway when they
    // jump to a nil IMP. We don't care if that jump also fails ptrauth.
    AuthAndResignAsIMP x17, x10, x1, x16    // authenticate imp and re-sign as IMP
    cmp x16, x15
    cinc    x16, x16, ne            // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
    ret             // return imp via x17
.else
.abort oops
.endif
.endmacro

    // $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    eor $0, $0, $3
    br  $0
.endmacro

imp = imp ^ cls 从bucket的imp中解码出真正的imp,并执行

objc_msgSendUncached

慢速查找流程,又是很大一个篇幅,下篇再说

流程图 objc_msgSend.png

思考一个问题:为什么objc_msgSend要用汇编实现?

相关文章

网友评论

      本文标题:OC底层原理-objc_msgSend流程分析上

      本文链接:https://www.haomeiwen.com/subject/anmcultx.html