008-消息的快速查找(下)

788 阅读5分钟

通过这篇文章可以获得什么

流程的简化回顾

第一:判断当前接收者是否存在

cmp	p0, #0

第二:通过isa拿到class

GetClassFromIsa_p16 p13, 1, x0

第三:进入CacheLookup流程,两种结果,找到并抛出imp,没找到通过__objc_msgSend_uncached进入后续的慢速查找流程

CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

第四:LLookupStart,查找开始,这里分解真机arm64架构,A12以上芯片汇编,下面只列出满足此条件的汇编

//arm64架构
CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//真机
CONFIG_USE_PREOPT_CACHES = 1
//是判断编译器是否支持指针身份验证功能,针对arm64e
//A12以上芯片(支持arm64e结构)
 __has_feature(ptrauth_calls)

第五:将isa偏移16个字节,得到cache_t

ldr	p11, [x16, #CACHE]

第六:判断cache_t0号位置是否为0,如果不为0,执行LLookupPreopt,去找共享缓存,如果为0,将cache_t(_bucketsAndMaybeMask) & #0x0000ffffffffffff,得到了bucekts

tbnz	p11, #0, LLookupPreopt\Function
and	p10, p11, #0x0000ffffffffffff	// p10 = buckets

第七:将sel右移7位在与自己做逻辑异或^赋值给p12,将cache_t(_bucketsAndMaybeMask)右移48位,即清空了buckets,可以得到mask,最后将p12 & mask,得到了第一次查找bucket_t的index,即first_probed

eor	p12, p1, p1, LSR #7
and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask

对于第七步的源码解释:

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}

第八:有第七步得知第一个查找的bucket_t的下标index,那么接下来就是如何偏移到这个bucket_t拿到里面的selimp,这里的做法是将index向左偏移4位,可以理解为index * (1 << 4)index * 16,得到的是偏移距离,为什么偏移4位,因为一个bucket_t占16字节,最后在加上buckets地址,就得到了第一个要比较的bucket_t

add	p13, p10, p12, LSL #(1+PTRSHIFT)

第九:将p13内的impsel拿出来分别存在p17p9,执行bucket--。如果sel != _cmd,执行3f,判断sel是否存在,有值,将bucket(当前)与bucekts(bucket首位)比较,如果>0,继续循环,回到1b,执行bucket--。如果这个过程找到了目标_cmd,跳转到2,命中缓存,结束查找。

1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	
	cmp	p9, p1				
	b.ne	3f				
2:	CacheHit \Mode				
3:	cbz	p9, \MissLabelDynamic		
	cmp	p13, p10			
	b.hs	1b

如果找到bucket首位都未匹配到目标_cmd,则直接跳转至mask处bucket(末位bucket_t),查找缓存内剩余的buckets,这里还获取了第一次探查的位置index,为了避免重复探查4内操作为将bucket_timpsel拿出来赋值给p17p9bucket--,比较sel与_cmd,如果成功,执行2b,命中缓存,判断p9是否存在,判断当前bucket是否大于第一次探查的bucket,如果上述条件都满足,则继续循环4b,直到命中缓存。如果全部都搜索完还没有命中,进入慢速查找流程

add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
add	p12, p10, p12, LSL #(1+PTRSHIFT)
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

真机调试(汇编分析)

案例源码:

@interface FFPerson : NSObject
- (void)likeGirls;
@end

@implementation FFPerson
- (void)likeGirls{
    NSLog(@"%s",__func__);
}
@end
int main(int argc, char * argv[]) {
    @autoreleasepool {

        FFPerson *person = [FFPerson alloc];
        [person likeGirls];
        
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

确认当前探索的类与方法

真机调试-确认当前类与方法.png

真机汇编分解图解:

真机调试汇编分解.png

关于类cache_t内存扩容在真机情况下的源码模拟

Demo地址

部分关键源码:

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient

//preopt_cache_entry_t源码模仿
struct ff_preopt_cache_entry_t {
    uint32_t sel_offs;
    uint32_t imp_offs;
};

//preopt_cache_t源码模仿
struct ff_preopt_cache_t {
    int32_t  fallback_class_offset;
    union {
        struct {
            uint16_t shift       :  5;
            uint16_t mask        : 11;
        };
        uint16_t hash_params;
    };
    uint16_t occupied    : 14;
    uint16_t has_inlines :  1;
    uint16_t bit_one     :  1;
    struct ff_preopt_cache_entry_t entries;
    
    inline int capacity() const {
        return mask + 1;
    }
};

//bucket_t源码模仿
struct ff_bucket_t {
    IMP _imp;
    SEL _sel;
};

//cache_t源码模仿
struct ff_cache_t {
    uintptr_t _bucketsAndMaybeMask; // 8
    struct ff_preopt_cache_t _originalPreoptCache; // 8

    static constexpr uintptr_t maskShift = 48;

    static constexpr uintptr_t maskZeroBits = 4;

    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    static constexpr uintptr_t preoptBucketsMarker = 1ul;

    static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
    
    ff_bucket_t *buckets() {
        return (ff_bucket_t *)(_bucketsAndMaybeMask & bucketsMask);
    }
    
    uint32_t mask() const {
        return _bucketsAndMaybeMask >> maskShift;
    }
    
};

//class_data_bits_t源码模仿
struct ff_class_data_bits_t {
    uintptr_t objc_class;
};

//类源码模仿
struct ff_objc_class {
    Class isa;
    Class superclass;
    struct ff_cache_t cache;
    struct ff_class_data_bits_t bits;
};


void test(Class cls) {
    
    //将person的类型转换成自定义的源码ff_objc_class类型,方便后续操作
    struct ff_objc_class *pClass = (__bridge struct ff_objc_class *)(cls);
    
    struct ff_cache_t cache = pClass->cache;
    struct ff_bucket_t * buckets = cache.buckets();
    struct ff_preopt_cache_t origin = cache._originalPreoptCache;
    uintptr_t _bucketsAndMaybeMask = cache._bucketsAndMaybeMask;
    uintptr_t mask = cache.mask();
    
    
    
    NSLog(@"class: %p", pClass);
    NSLog(@"_bucketsAndMaybeMask: 0x%lx, mask: %lu", _bucketsAndMaybeMask, mask);
    
    //打印当前有多少个方法缓存与最大缓存数量
    NSLog(@"%u-%u",origin.occupied,origin.capacity());
    
    //打印buckets
    for (int i = 0; i < mask + 1; i++ ) {
        SEL sel = buckets[i]._sel;
        IMP imp = buckets[i]._imp;
        NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
}

int main(int argc, char * argv[]) {

    @autoreleasepool {
        
        //给person分配内存
        FFPerson *person = [FFPerson alloc];
        //调用方法
        [person likeGirls];
        test(person.class);
        [person likeFoods];
        test(person.class);
        [person likeflower];
        test(person.class);
        [person likeStudy];
        test(person.class);
        [person enjoyLift];
        test(person.class);
        [person lnspireCreativity];
        test(person.class);
        
       ......

    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

打印结果

2021-06-30 23:38:39.523507+0800 002-cache真机源码还原[2001:757686] -[FFPerson likeGirls]
2021-06-30 23:38:39.524594+0800 002-cache真机源码还原[2001:757686] class: 0x102ef9680
2021-06-30 23:38:39.524617+0800 002-cache真机源码还原[2001:757686] _bucketsAndMaybeMask: 0x10002835b60e0, mask: 1
2021-06-30 23:38:39.524633+0800 002-cache真机源码还原[2001:757686] 1-1025
2021-06-30 23:38:39.524651+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.524706+0800 002-cache真机源码还原[2001:757686] likeGirls-0xe00f9f8102ef148c
2021-06-30 23:38:39.524728+0800 002-cache真机源码还原[2001:757686] -[FFPerson likeFoods]
2021-06-30 23:38:39.524868+0800 002-cache真机源码还原[2001:757686] class: 0x102ef9680
2021-06-30 23:38:39.525038+0800 002-cache真机源码还原[2001:757686] _bucketsAndMaybeMask: 0x10002835b60e0, mask: 1
2021-06-30 23:38:39.525138+0800 002-cache真机源码还原[2001:757686] 2-1025
2021-06-30 23:38:39.525253+0800 002-cache真机源码还原[2001:757686] likeFoods-0xf64c0b0102ef1504
2021-06-30 23:38:39.525279+0800 002-cache真机源码还原[2001:757686] likeGirls-0xe00f9f8102ef148c
2021-06-30 23:38:39.525298+0800 002-cache真机源码还原[2001:757686] -[FFPerson likeflower]
2021-06-30 23:38:39.525312+0800 002-cache真机源码还原[2001:757686] class: 0x102ef9680
2021-06-30 23:38:39.525326+0800 002-cache真机源码还原[2001:757686] _bucketsAndMaybeMask: 0x30002820b4c40, mask: 3
2021-06-30 23:38:39.525437+0800 002-cache真机源码还原[2001:757686] 1-1025
2021-06-30 23:38:39.525517+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.525783+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.525910+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.526071+0800 002-cache真机源码还原[2001:757686] likeflower-0x2d78768102ef14c8
2021-06-30 23:38:39.526236+0800 002-cache真机源码还原[2001:757686] -[FFPerson likeStudy]
2021-06-30 23:38:39.526347+0800 002-cache真机源码还原[2001:757686] class: 0x102ef9680
2021-06-30 23:38:39.526438+0800 002-cache真机源码还原[2001:757686] _bucketsAndMaybeMask: 0x30002820b4c40, mask: 3
2021-06-30 23:38:39.526548+0800 002-cache真机源码还原[2001:757686] 2-1025
2021-06-30 23:38:39.526643+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.526745+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.526900+0800 002-cache真机源码还原[2001:757686] likeStudy-0xf26c730102ef1540
2021-06-30 23:38:39.527019+0800 002-cache真机源码还原[2001:757686] likeflower-0x2d78768102ef14c8
2021-06-30 23:38:39.527098+0800 002-cache真机源码还原[2001:757686] -[FFPerson enjoyLift]
2021-06-30 23:38:39.527195+0800 002-cache真机源码还原[2001:757686] class: 0x102ef9680
2021-06-30 23:38:39.527316+0800 002-cache真机源码还原[2001:757686] _bucketsAndMaybeMask: 0x30002820b4c40, mask: 3
2021-06-30 23:38:39.527427+0800 002-cache真机源码还原[2001:757686] 3-1025
2021-06-30 23:38:39.527520+0800 002-cache真机源码还原[2001:757686] (null)-0x0
2021-06-30 23:38:39.527626+0800 002-cache真机源码还原[2001:757686] enjoyLift-0x8e617e8102ef157c
2021-06-30 23:38:39.527728+0800 002-cache真机源码还原[2001:757686] likeStudy-0xf26c730102ef1540
2021-06-30 23:38:39.527829+0800 002-cache真机源码还原[2001:757686] likeflower-0x2d78768102ef14c8
2021-06-30 23:38:39.528005+0800 002-cache真机源码还原[2001:757686] -[FFPerson lnspireCreativity]
2021-06-30 23:38:39.528130+0800 002-cache真机源码还原[2001:757686] class: 0x102ef9680
2021-06-30 23:38:39.528236+0800 002-cache真机源码还原[2001:757686] _bucketsAndMaybeMask: 0x30002820b4c40, mask: 3
2021-06-30 23:38:39.528349+0800 002-cache真机源码还原[2001:757686] 4-1025
2021-06-30 23:38:39.528450+0800 002-cache真机源码还原[2001:757686] lnspireCreativity-0xc6607b8102ef15b8
2021-06-30 23:38:39.528536+0800 002-cache真机源码还原[2001:757686] enjoyLift-0x8e617e8102ef157c
2021-06-30 23:38:39.528705+0800 002-cache真机源码还原[2001:757686] likeStudy-0xf26c730102ef1540
2021-06-30 23:38:39.528804+0800 002-cache真机源码还原[2001:757686] likeflower-0x2d78768102ef14c8

INIT_CACHE_SIZE_LOG2

#if CACHE_END_MARKER || (__arm64__ && !__LP64__)
    INIT_CACHE_SIZE_LOG2 = 2,
#else
    INIT_CACHE_SIZE_LOG2 = 1,
#endif

cache_fill_ratio

#if __arm__  ||  __x86_64__  ||  __i386__

#define CACHE_END_MARKER 1

static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 3 / 4;
}

#elif __arm64__ && !__LP64__

#define CACHE_END_MARKER 0

static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 3 / 4;
}

#elif __arm64__ && __LP64__

#define CACHE_END_MARKER 0

static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 7 / 8;
}

#define CACHE_ALLOW_FULL_UTILIZATION 1

#else
#error unknown architecture
#endif

结论:

  1. iPhoneXs,初始缓存容量为2(INIT_CACHE_SIZE = (1 << 1))
  2. 真机扩容按照7/8的规则,2倍扩容,所以扩容规律2-> 4 -> 8 -> 16 -> 2^n
  3. x86_64架构初始容量为4(INIT_CACHE_SIZE = (1 << 2))
  4. 模拟器扩容按照3/4的规则,2倍扩容,扩容规律4 -> 8 -> 16 -> 2^(n+1)

拓展

源码释义补充

objc底层操作预留位,判断是否有preopt_cache_t,是给objc_msgSend使用的

static constexpr uintptr_t maskZeroBits = 4;

汇编指令:

本篇内容涉及的部分汇编指令:

指令寄存器值说明
mov x0, #0xffff0xffff将0xffff放到寄存器x0
movk x0, #0x3a44, lsl #160x3a44ffff将0x3a44放到寄存器x0,从16bit位开始存放(movk 保持其他位置不变,将值存入指定bit位)
tbnz x0, #0x0, 0x199c73240......判断x0的1号位置是否为0,不为零,跳转到0x199c73240,为0,向下执行
eor x10, x10, x1x10将x10与x1逻辑异或,结果存在x10
ldr x13, [x0]x13将x0寄存器中的值取出放在x13寄存器
and x10, x11, #0xffffffffffffx10将x11 & 0xffffffffffff,结果存在x10
cmp x9, x1......比较x9和x1的大小
b.ne 0x199c73210......不相等,跳转至0x199c73210
b.hs 0x199c731f8......无符号小于,跳转至0x199c731f8
b.hi 0x199c73224......无符号大于,跳转至0x199c73224
b.hi 0x199c73224......无符号大于,跳转至0x199c73224
b.eq 0x199c73204......等于,跳转至0x199c73204
cbz x9, 0x199c73600......判断x9是否存在,不存在,则跳转0x199c73600