voidcache_fill(Class cls, SEL sel, IMP imp, id receiver) { // 因为cache_t内部用来储存的结构其实就是个数组 // 所以操作的时候需要先加个锁,保证线程安全。 mutex_locker_tlock(cacheUpdateLock); cache_fill_nolock(cls, sel, imp, receiver); }
staticvoidcache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) { cacheUpdateLock.assertLocked();
// Never cache before +initialize is done if (!cls->isInitialized()) return; //如果类未初始化
// Make sure the entry wasnot added to the cache by some other thread // before we grabbed the cacheUpdateLock. //判断方法是否已经缓存了,如果缓存了,直接返回,如果没有,则进入下面的逻辑进行缓存。 if (cache_getImp(cls, sel)) return;
// Use the cache as-is if it is less than 3/4 full // 通过cache获取已经缓存的数量occupied,并且+1,得到新的缓存数量。 mask_t newOccupied = cache->occupied() + 1; mask_t capacity = cache->capacity(); if (cache->isConstantEmptyCache()) { // Cache is read-only. Replace it. cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE); } elseif (newOccupied <= capacity / 4 * 3) { // Cache is less than 3/4 full. Use it as-is. } else { // Cache is too full. Expand it. cache->expand();//如果大于3/4,就需要expand()即缓存扩容。 }
// Scan for the first unused slot and insert there. // There is guaranteed to be an empty slot because the // minimum size is 4 and we resized at 3/4 full. // 根据sel获取bucket,此bucket的sel一般为0(说明这个位置还没缓存方法), // 也可能与实参sel相等(hash冲突,可能性很低) bucket_t *bucket = cache->find(sel, receiver); // 当且仅当bucket的sel为0时,执行_occupied++ if (bucket->sel() == 0) cache->incrementOccupied(); // 更新bucket的sel和imp bucket->set<Atomic>(sel, imp); }
if ((uint32_t)(mask_t)newCapacity != newCapacity) { // mask overflow - can't grow further // fixme this wastes one bit of mask newCapacity = oldCapacity; } reallocate(oldCapacity, newCapacity); }
cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) jne 1f // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp
1: // loop cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++ 2: cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp
3: // wrap or miss jb LCacheMiss_f // if (bucket->sel < 1) cache miss // wrap movq cached_imp(%r11), %r11 // bucket->imp is really first bucket jmp 2f
// Clone scanning loop to miss instead of hang when cache is corrupt. // The slow path may detect any corruption and halt later.
1: // loop cmpq $$1, cached_sel(%r11) jbe 3f // if (bucket->sel <= 1) wrap or miss
addq $$16, %r11 // bucket++ 2: cmpq cached_sel(%r11), %a2 // if (bucket->sel != _cmd) jne 1b // scan more // CacheHit must always be preceded by a not-taken `jne` instruction CacheHit $0, $1 // call or return imp