Skip to content

Commit 796e2f8

Browse files
committed
Improve CheckIfTypeIsEquivalent
`CheckIfTypeIsEquivalent` helper is called from JITted code to perform equivalent type check. It validates if the object's type is present in one of the 8 cached equivalent types and if yes, updates the guard value and return back to JITted code. If not, it takes slow path and verifies the prototype of object to that of cached type. However if guard is not valid, we would just bailout `BailOutFailedEquivalentTypeCheck`. Today in `PreSweepCallback` we would invalidate the guard if the underline type is not marked. However we could have invalid guard value but if there are alive types present in equivTypes[], we could use them to check if `type` in question matches any of them and if yes, update the guard value, so next time JIT code will be able to take advantage of it next time. On instrumenting code, noticed that there are high frequency that this happens (at least in acmeair) where we have valid equivTypes[], but we don'take take its advantage just because guard value is invalid. This gives approx. 2.5 % win in acmeair. Other benchmarks are flat. Also, I noticed that the cache eviction policy was kicking out valid caches despite of having empty slots in types[] where new type could have been stored. Modified the eviction logic to take advantage of empty slots before going to existing eviction logic. I have also added logic to compact the equivTypes by moving non-null types towards front of array. Test: Internal tools pass Perf: Standard benchmarks are almost flat with minor improvement in Octane, Kraken. Acme-air shows 2.5% win.
1 parent e04f0b3 commit 796e2f8

File tree

4 files changed

+140
-44
lines changed

4 files changed

+140
-44
lines changed

lib/Runtime/Base/FunctionBody.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8775,14 +8775,17 @@ namespace Js
87758775
Type *type = reinterpret_cast<Type*>(this->guard->GetValue());
87768776
if (!recycler->IsObjectMarked(type))
87778777
{
8778-
this->guard->Invalidate();
8778+
this->guard->InvalidateDuringSweep();
87798779
}
87808780
else
87818781
{
87828782
isAnyTypeLive = true;
87838783
}
87848784
}
8785-
8785+
uint16 nonNullIndex = 0;
8786+
#if DBG
8787+
bool isGuardValuePresent = false;
8788+
#endif
87868789
for (int i = 0; i < EQUIVALENT_TYPE_CACHE_SIZE; i++)
87878790
{
87888791
Type *type = this->types[i];
@@ -8794,11 +8797,29 @@ namespace Js
87948797
}
87958798
else
87968799
{
8800+
// compact the types array by moving non-null types
8801+
// at the beginning.
8802+
this->types[nonNullIndex++] = type;
87978803
isAnyTypeLive = true;
8804+
#if DBG
8805+
isGuardValuePresent = this->guard->GetValue() == reinterpret_cast<intptr_t>(type) ? true : isGuardValuePresent;
8806+
#endif
87988807
}
87998808
}
88008809
}
8810+
if (nonNullIndex > 0)
8811+
{
8812+
memset((void*)(this->types + nonNullIndex), 0, sizeof(Js::Type*) * (EQUIVALENT_TYPE_CACHE_SIZE - nonNullIndex));
8813+
}
8814+
else if(guard->IsInvalidatedDuringSweep())
8815+
{
8816+
// just mark this as actual invalidated since there are no types
8817+
// present
8818+
guard->Invalidate();
8819+
}
88018820

8821+
// verify if guard value is valid, it is present in one of the types
8822+
AssertMsg(!this->guard->IsValid() || isGuardValuePresent, "After ClearUnusedTypes, valid guard value should be one of the cached equivalent types.");
88028823
return isAnyTypeLive;
88038824
}
88048825

lib/Runtime/Base/FunctionBody.h

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,53 @@ namespace Js
7373
{
7474
friend class PropertyGuardValidator;
7575
private:
76+
enum GuardValue : intptr_t {
77+
Invalidated = 0,
78+
Uninitialized = 1,
79+
Invalidated_DuringSweep = 2
80+
};
7681
intptr_t value;
82+
#if DBG
83+
bool wasReincarnated = false;
84+
#endif
7785
public:
7886
static PropertyGuard* New(Recycler* recycler) { return RecyclerNewLeaf(recycler, Js::PropertyGuard); }
79-
PropertyGuard() : value(1) {}
80-
PropertyGuard(intptr_t value) : value(value) { Assert(this->value != 0); }
87+
PropertyGuard() : value(GuardValue::Uninitialized) {}
88+
PropertyGuard(intptr_t value) : value(value)
89+
{
90+
// GuardValue::Invalidated and GuardValue::Invalidated_DuringSweeping can only be set using
91+
// Invalidate() and InvalidatedDuringSweep() methods respectively.
92+
Assert(this->value != GuardValue::Invalidated && this->value != GuardValue::Invalidated_DuringSweep);
93+
}
8194

8295
inline static size_t const GetSizeOfValue() { return sizeof(((PropertyGuard*)0)->value); }
8396
inline static size_t const GetOffsetOfValue() { return offsetof(PropertyGuard, value); }
8497

8598
intptr_t GetValue() const { return this->value; }
86-
bool IsValid() { return this->value != 0; }
87-
void SetValue(intptr_t value) { Assert(value != 0); this->value = value; }
99+
bool IsValid()
100+
{
101+
return this->value != GuardValue::Invalidated && this->value != GuardValue::Invalidated_DuringSweep;
102+
}
103+
bool IsInvalidatedDuringSweep() { return this->value == GuardValue::Invalidated_DuringSweep; }
104+
void SetValue(intptr_t value)
105+
{
106+
// GuardValue::Invalidated and GuardValue::Invalidated_DuringSweeping can only be set using
107+
// Invalidate() and InvalidatedDuringSweep() methods respectively.
108+
Assert(value != GuardValue::Invalidated && value != GuardValue::Invalidated_DuringSweep);
109+
this->value = value;
110+
}
88111
intptr_t const* GetAddressOfValue() { return &this->value; }
89-
void Invalidate() { this->value = 0; }
112+
void Invalidate() { this->value = GuardValue::Invalidated; }
113+
void InvalidateDuringSweep()
114+
{
115+
#if DBG
116+
wasReincarnated = true;
117+
#endif
118+
this->value = GuardValue::Invalidated_DuringSweep;
119+
}
120+
#if DBG
121+
bool WasReincarnated() { return this->wasReincarnated; }
122+
#endif
90123
};
91124

92125
class PropertyGuardValidator

lib/Runtime/Language/FunctionCodeGenJitTimeData.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -667,20 +667,17 @@ namespace Js
667667

668668
if (PHASE_TRACE(Js::ObjTypeSpecPhase, topFunctionBody) || PHASE_TRACE(Js::EquivObjTypeSpecPhase, topFunctionBody))
669669
{
670-
if (PHASE_TRACE(Js::ObjTypeSpecPhase, topFunctionBody) || PHASE_TRACE(Js::EquivObjTypeSpecPhase, topFunctionBody))
670+
if (typeSet)
671671
{
672-
if (typeSet)
672+
const PropertyRecord* propertyRecord = scriptContext->GetPropertyName(propertyId);
673+
Output::Print(_u("Created ObjTypeSpecFldInfo: id %u, property %s(#%u), slot %u, type set: "),
674+
id, propertyRecord->GetBuffer(), propertyId, slotIndex);
675+
for (uint16 ti = 0; ti < typeCount - 1; ti++)
673676
{
674-
const PropertyRecord* propertyRecord = scriptContext->GetPropertyName(propertyId);
675-
Output::Print(_u("Created ObjTypeSpecFldInfo: id %u, property %s(#%u), slot %u, type set: "),
676-
id, propertyRecord->GetBuffer(), propertyId, slotIndex);
677-
for (uint16 ti = 0; ti < typeCount - 1; ti++)
678-
{
679-
Output::Print(_u("0x%p, "), typeSet->GetType(ti));
680-
}
681-
Output::Print(_u("0x%p\n"), typeSet->GetType(typeCount - 1));
682-
Output::Flush();
677+
Output::Print(_u("0x%p, "), typeSet->GetType(ti));
683678
}
679+
Output::Print(_u("0x%p\n"), typeSet->GetType(typeCount - 1));
680+
Output::Flush();
684681
}
685682
}
686683

lib/Runtime/Language/JavascriptOperators.cpp

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8197,9 +8197,9 @@ namespace Js
81978197
return false;
81988198
}
81998199

8200-
if (guard->GetType()->GetScriptContext() != type->GetScriptContext())
8200+
if (!guard->IsInvalidatedDuringSweep() && guard->GetType()->GetScriptContext() != type->GetScriptContext())
82018201
{
8202-
// Can't cache cross-context objects
8202+
// For valid guard value, can't cache cross-context objects
82038203
return false;
82048204
}
82058205

@@ -8215,6 +8215,17 @@ namespace Js
82158215
if (type == equivTypes[0] || type == equivTypes[1] || type == equivTypes[2] || type == equivTypes[3] ||
82168216
type == equivTypes[4] || type == equivTypes[5] || type == equivTypes[6] || type == equivTypes[7])
82178217
{
8218+
#if DBG
8219+
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
8220+
{
8221+
if (guard->WasReincarnated())
8222+
{
8223+
Output::Print(_u("EquivObjTypeSpec: Guard 0x%p was reincarnated and working now \n"), guard);
8224+
Output::Flush();
8225+
}
8226+
}
8227+
#endif
8228+
82188229
guard->SetType(type);
82198230
return true;
82208231
}
@@ -8233,6 +8244,12 @@ namespace Js
82338244
Type* refType = equivTypes[0];
82348245
if (refType == nullptr)
82358246
{
8247+
#if DBG
8248+
for (int i = 1;i < EQUIVALENT_TYPE_CACHE_SIZE;i++)
8249+
{
8250+
AssertMsg(equivTypes[i] == nullptr, "In equiv typed caches, if first element is nullptr, all others should be nullptr");
8251+
}
8252+
#endif
82368253
return false;
82378254
}
82388255

@@ -8292,38 +8309,66 @@ namespace Js
82928309
return false;
82938310
}
82948311

8295-
// CONSIDER (EquivObjTypeSpec): Invent some form of least recently used eviction scheme.
8296-
uintptr_t index = (reinterpret_cast<uintptr_t>(type) >> 4) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8297-
if (cache->nextEvictionVictim == EQUIVALENT_TYPE_CACHE_SIZE)
8312+
int emptySlotIndex = -1;
8313+
for (int i = 0;i < EQUIVALENT_TYPE_CACHE_SIZE;i++)
82988314
{
8299-
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8300-
if (equivTypes[index] != nullptr)
8315+
if (equivTypes[i] == nullptr)
83018316
{
8302-
uintptr_t initialIndex = index;
8303-
index = (initialIndex + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8304-
for (; index != initialIndex; index = (index + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1))
8305-
{
8306-
if (equivTypes[index] == nullptr) break;
8307-
}
8308-
}
8309-
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8310-
if (equivTypes[index] != nullptr)
8317+
emptySlotIndex = i;
8318+
break;
8319+
};
8320+
}
8321+
8322+
// We have some empty slots, let us use those first
8323+
if (emptySlotIndex != -1)
8324+
{
8325+
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
83118326
{
8312-
cache->nextEvictionVictim = 0;
8327+
Output::Print(_u("EquivObjTypeSpec: Saving type in unused slot of equiv types cache. \n"));
8328+
Output::Flush();
83138329
}
8330+
equivTypes[emptySlotIndex] = type;
83148331
}
83158332
else
83168333
{
8317-
Assert(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
8318-
__analysis_assume(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
8319-
equivTypes[cache->nextEvictionVictim] = equivTypes[index];
8320-
cache->nextEvictionVictim = (cache->nextEvictionVictim + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8321-
}
8322-
8323-
Assert(index < EQUIVALENT_TYPE_CACHE_SIZE);
8324-
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8325-
equivTypes[index] = type;
8334+
// CONSIDER (EquivObjTypeSpec): Invent some form of least recently used eviction scheme.
8335+
uintptr_t index = (reinterpret_cast<uintptr_t>(type) >> 4) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8336+
if (cache->nextEvictionVictim == EQUIVALENT_TYPE_CACHE_SIZE)
8337+
{
8338+
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8339+
if (equivTypes[index] != nullptr)
8340+
{
8341+
uintptr_t initialIndex = index;
8342+
index = (initialIndex + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8343+
for (; index != initialIndex; index = (index + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1))
8344+
{
8345+
if (equivTypes[index] == nullptr) break;
8346+
}
8347+
}
8348+
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8349+
if (equivTypes[index] != nullptr)
8350+
{
8351+
cache->nextEvictionVictim = 0;
8352+
}
8353+
}
8354+
else
8355+
{
8356+
Assert(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
8357+
__analysis_assume(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
8358+
equivTypes[cache->nextEvictionVictim] = equivTypes[index];
8359+
cache->nextEvictionVictim = (cache->nextEvictionVictim + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8360+
}
83268361

8362+
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
8363+
{
8364+
Output::Print(_u("EquivObjTypeSpec: Saving type in used slot of equiv types cache at index = %d. NextEvictionVictim = %d. \n"), index, cache->nextEvictionVictim);
8365+
Output::Flush();
8366+
}
8367+
Assert(index < EQUIVALENT_TYPE_CACHE_SIZE);
8368+
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8369+
equivTypes[index] = type;
8370+
}
8371+
83278372
// Fixed field checks allow us to assume a specific type ID, but the assumption is only
83288373
// valid if we lock the type. Otherwise, the type ID may change out from under us without
83298374
// evolving the type.

0 commit comments

Comments
 (0)