preface

I explored cache_t and method caching earlier, so it’s reasonable to explore method lookups next, but we need to understand the nature of rutime and methods before exploring method lookups.

A, the runtime

What is Runtime? The short answer is runtime. There’s another kind of compile time.

  • Compile timeJust doing some translation work, like checking if you’ve accidentally misspelled any keywords. What about lexical analysis, grammatical analysis, that sort of thing. Just like a teacher checking a student’s essay for typos and bad sentences, the compiler will tell you if it finds any. If you are using Microsoft VS, click build and start compiling. If errors or warning is displayed below, it is checked by the compiler. This is called a compile-time error, and any type checking done during this process is also called compile-time type checking, or static type checking
  • The runtimeThe code runs, gets loaded into memory, andThe runtimeType checking is different from compile-time type checking (or static type checking) described earlier. It’s not just scanning code. It’s doing things in memory, making judgments.

A set of assembler, C, and C ++ apis that provide runtime for Objective-C.

1, version,

Runtime comes in two versions: Legacy (earlier versions) and Modern (current versions).

  • Programming interfaces for earlier versions:Objective - 1.0 CFor 32-bit Mac OS X platforms;
  • Programming interface of current version:Objective - 2.0 C, iPhone programs and 64-bit programs for Mac OS X V10.5 and later.

2. Tuning mode

There are three ways to tune up the Runtime:

  • OCLevel:[[NSObkect alloc] init];
  • NSObjectMethods defined in class:performSelector:withObject:;
  • Runtime API:objc_msgSend(...) ;

Second, the nature of the method

Let’s start by creating a class and adding methods, as shown below:Then clang:clang -rewrite-objc main.m -o main.cppOpen generatedmain.cppAnd findmainFunction entry, as shown in figure:To simplify this slightly, see the following figure:It is not hard to see that the so-called method is at the bottomobjc_msgSendSending a message. The parameter is the message receiverpandselAnd parameters.

Conclusion: The essence of the method is objc_msgSend.

Third, objc_msgSend

We have discovered that the essence of the method is objc_msgSend, now let’s explore it.

1. Source code analysis

First we go intoobjc_msgSendAs shown in figure:At this point we find that we can’t get into the implementation, so think about it:runtimeIs made up ofassembly,c,c++Constitute a set ofObjective-CProvides run-timeAPI.So we can look for an assembly implementation, as shown:

645 results out of 23 files, which is kind of scary. 645 results out of 23 files, which is kind of scary. But remember we’re looking for assembly, yes.sFile, simple processing, as shown in the figure:these.sFiles areobjc_msgSendImplementation, according to the CPU architecture of the different assembly syntax is also different, but the implementation process is basically the same, we choosearm64As shown in the figure:After opening it, I found it was still a lotENTRY _objc_msgSendAs shown in figure:General process:

  1. Judge:
1.1 Whether the receiver is empty; 1.2 Determine the small object. If the small object is empty, LReturnZero is returned directly. Execute LNilOrTagged for small objects. 1.3 LNilOrTagged determines whether the receiver is null again. If the receiver is null, LReturnZero is returned. GetTaggedClass, then jump to LGetIsaDone; If the object is neither small nor empty, GetClassFromIsa_p16 gets the class and holds p16Copy the code

GetClassFromIsa_p16As shown in figure:A slight translation of the notes.

  1. LGetIsaDone

    This is basically just a cache hook up for quick lookup.

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
//   As soon as we're past the LLookupStart\Function label we may have
//   loaded an invalid cache pointer or mask.
//
//   When task_restartable_ranges_synchronize() is called,
//   (or when a signal hits us) before we're past LLookupEnd\Function,
//   then our PC will be reset to LLookupRecover\Function which forcefully
//   jumps to the cache-miss codepath which have the following
//   requirements:
//
//   GETIMP:
//     The cache-miss is just returning NULL (setting x0 to 0)
//
//   NORMAL and LOOKUP:
//   - x0 contains the receiver
//   - x1 contains the selector
//   - x16 contains the isa
//   - other registers are set as per calling conventions

//一旦我们超过了LLookupStart\Function标签,我们就可能加载了一个无效的缓存指针或掩码。

//当task_restartable_ranges_synchronize()被调用时,
//(或者当一个信号击中我们)在我们经过LLookupEnd\Function之前,
//然后我们的PC将被重置为LLookupRecover\Function,
//它会强制跳转到cache-miss代码路径,代码路径有以下要求:

//GETIMP:
//缓存未命中只是返回NULL(将x0设置为0)

//正常和查找:
//- x0表示接收端
//-x1包含选择器
//- x16包含isa
//-其他寄存器按照调用约定设置

mov x15, x16 // stash the original isa 将x16的值,赋值给x15
LLookupStart\Function:
// p1 = SEL, p16 = isa => isa -> 类的 isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
  ldr p10, [x16, #CACHE] // p10 = mask|buckets
  lsr p11, p10, #48 // p11 = mask
  and p10, p10, #0xffffffffffff // p10 = buckets
  and w12, w1, w11 // x12 = _cmd & mask
  
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // x16(isa) 平移 16 就得到了 cache的地址(即为_bucketsAndMaybeMask的地址)
    // 将 _bucketsAndMaybeMask的地址存入 p11 寄存器中。
  ldr p11, [x16, #CACHE] // p11 = mask|buckets
  
#if CONFIG_USE_PREOPT_CACHES

#if __has_feature(ptrauth_calls)
  tbnz p11, #0, LLookupPreopt\Function
  and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
  and p10, p11, #0x0000fffffffffffe // p10 = buckets
  // 如果_bucketsAndMaybeMask第 0 位不等于 0,就跳转到 LLookupPreopt\Function
  tbnz p11, #0, LLookupPreopt\Function
  
#endif
  eor p12, p1, p1, LSR #7
  and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
  
#else
  and p10, p11, #0x0000ffffffffffff // p10 = buckets
  // 通过哈希求 index 下标 赋给p12
  and p12, p1, p11, LSR #48 // x12 = _cmd & mask
  
#endif // CONFIG_USE_PREOPT_CACHES

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
  ldr p11, [x16, #CACHE] // p11 = mask|buckets
  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 p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (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
    // wrap-around:
    //   p10 = first bucket 第一个桶子
    //   p11 = mask (and maybe other bits on LP64) 掩码
    //   p12 = _cmd & mask 通过哈希求 index 下标 赋给p12
    //
    // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
    // So stop when we circle back to the first probed bucket
    // rather than when hitting the first bucket again.
    //
    //完全缓存可以通过CACHE_ALLOW_FULL_UTILIZATION实现。
    //当我们绕回第一个探测的桶时停止
    //而不是再次击中第一个桶。
    
    // Note that we might probe the initial bucket twice
    // when the first probed slot is the last entry.
    //注意,我们可能探测初始桶两次
    //当第一个被探测的槽是最后一个条目时。
    
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
  add p13, p10, w11, UXTW #(1+PTRSHIFT)
                    // p13 = buckets + (mask << 1+PTRSHIFT)

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
   add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                    // p13 = buckets + (mask << 1+PTRSHIFT)
                    // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p13, p10, p11, LSL #(1+PTRSHIFT)
            // p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
    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
#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
   and p10, p11, #0x007ffffffffffffe // p10 = buckets
   autdb x10, x16 // auth as early as possible
#endif
  // x12 = (_cmd - first_shared_cache_sel)
  adrp x9, _MagicSelRef@PAGE
  ldr p9, [x9, _MagicSelRef@PAGEOFF]
  sub p12, p1, p9
    // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
  lsr x17, x11, #55 // w17 = (hash_shift, ...)
  lsr w9, w12, w17 // >>= shift
  lsr x17, x11, #60 // w17 = mask_bits
  mov x11, #0x7fff
  lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
  and x9, x9, x11 // &= mask
#else
  // bits 63..53 of x11 is hash_mask
  // bits 52..48 of x11 is hash_shift
  lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
  lsr w9, w12, w17 // >>= shift
  and x9, x9, x11, LSR #53 // &=  mask
#endif
  ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
  cmp x12, w17, uxtw

.if \Mode == GETIMP 
  b.ne \MissLabelConstant // cache miss
  sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
  SignAsImp x0
  ret
  
.else
  b.ne 5f // cache miss
  sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
  
.if \Mode == NORMAL
  br x17
  
.elseif \Mode == LOOKUP
  orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
  SignAsImp x17
  ret

.else
  .abort  unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
  add x16, x16, x9 // compute the fallback isa
  b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES

.endmacro
Copy the code

The above code simply translates the following and some personal comments for the main system comments.