美文网首页
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