CacheLookup 宏:objc_msgSend 快路径的方法缓存探测

入口寄存器 p1 = SELp16 = isa。目标:在类的方法缓存哈希表里找到 {imp, sel} 匹配项。命中即跳进 IMP,扑空则转慢路径。下面按你笔记的四阶段拆解,并可逐步演示探测循环。

取 mask + buckets

按平台三选一读 CACHE 字段,拆出高 16 位 mask 与低 48 位 buckets,算哈希 _cmd&mask。

定位并探测

算出起始 bucket,进入 do-while 循环逐格比对 sel,向低地址方向探测。

wrap-around

探到表头还没中 → 绕到表尾继续扫,直到回到最初探测位才停。

预优化缓存

仅 dyld 共享缓存里的系统类走此路径,普通动态类用不到。

探测循环交互演示 do-while + wrap-around

选一个要查找的 selector,逐步执行哈希定位与逐格比对,观察命中 / 扑空 / 回绕三种结局。

p1 = bark mask = 7 hash = _cmd & mask = 3 p13 → bucket[3]
点击「下一步」开始:先用 _cmd & mask 定位起始 bucket。

核心循环对应的汇编

②的主探测循环与③的回绕循环,结构几乎一致,区别在于停止条件。

// ② 起始 bucket + do-while 探测 add p13, p10, p12, LSL #(1+PTRSHIFT) // p13 = 起始 bucket 1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp,sel}=*bucket-- cmp p9, p1 // sel == _cmd ? b.ne 3f // 不等 → 继续扫 2: CacheHit \Mode // 命中:跳/返回 imp 3: cbz p9, \MissLabelDynamic // 空槽 → 未命中(慢路径) cmp p13, p10 // bucket >= buckets ? b.hs 1b // 是 → 继续 do-while // ③ wrap-around:绕到表尾再扫一圈 add p13, p10, ...(mask) // p13 = 最后一个 bucket add p12, p10, p12, LSL #(1+PTRSHIFT) // 记下最初探测位 4: ldp p17, p9, [x13], #-BUCKET_SIZE cmp p9, p1 b.eq 2b // 命中 → 回到 hit cmp p9, #0 ccmp p13, p12, #0, ne // sel!=0 且 bucket>初探位 b.hi 4b b \MissLabelDynamic // 绕完整圈仍无 → 未命中

为什么探测方向是「向低地址递减」

指令 ldp p17, p9, [x13], #-BUCKET_SIZE 是「先读取,再把指针减一个 bucket(16 字节)」的后变址写法。所以探测从哈希起点向低地址走;当走到表头(p13 < p10/buckets)仍没命中,就进入③把指针重置到表尾,环形地再扫一段,直到绕回最初探测位置 p12 才宣告未命中——这正是开放寻址哈希表的环形探测。撞到 sel==0 的空槽则立即判定未命中,因为缓存里方法是连续填充的,空槽意味着不可能再有该项。