Skip to content

Commit 2c4b01f

Browse files
committed
[MERGE #1368 @kunalspathak] Improve CheckIfTypeIsEquivalent
Merge pull request #1368 from kunalspathak:typecheck `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.
2 parents 499ce40 + 475df2c commit 2c4b01f

File tree

4 files changed

+162
-52
lines changed

4 files changed

+162
-52
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: 58 additions & 6 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
@@ -181,15 +214,34 @@ namespace Js
181214
// so as to keep the cached types alive.
182215
EquivalentTypeCache* cache;
183216
uint32 objTypeSpecFldId;
217+
#if DBG
218+
// Intentionally have as intptr_t so this guard doesn't hold scriptContext
219+
intptr_t originalScriptContextValue = 0;
220+
#endif
184221

185222
public:
186223
JitEquivalentTypeGuard(Type* type, int index, uint32 objTypeSpecFldId):
187-
JitIndexedPropertyGuard(reinterpret_cast<intptr_t>(type), index), cache(nullptr), objTypeSpecFldId(objTypeSpecFldId) {}
224+
JitIndexedPropertyGuard(reinterpret_cast<intptr_t>(type), index), cache(nullptr), objTypeSpecFldId(objTypeSpecFldId)
225+
{
226+
#if DBG
227+
originalScriptContextValue = reinterpret_cast<intptr_t>(type->GetScriptContext());
228+
#endif
229+
}
188230

189231
Js::Type* GetType() const { return reinterpret_cast<Js::Type*>(this->GetValue()); }
190232

191233
void SetType(const Js::Type* type)
192234
{
235+
#if DBG
236+
if (originalScriptContextValue == 0)
237+
{
238+
originalScriptContextValue = reinterpret_cast<intptr_t>(type->GetScriptContext());
239+
}
240+
else
241+
{
242+
AssertMsg(originalScriptContextValue == reinterpret_cast<intptr_t>(type->GetScriptContext()), "Trying to set guard type from different script context.");
243+
}
244+
#endif
193245
this->SetValue(reinterpret_cast<intptr_t>(type));
194246
}
195247

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: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8197,24 +8197,52 @@ 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

82068206
// CONSIDER : Add stats on how often the cache hits, and simply force bailout if
82078207
// the efficacy is too low.
82088208

82098209
EquivalentTypeCache* cache = guard->GetCache();
8210-
82118210
// CONSIDER : Consider emitting o.type == equivTypes[hash(o.type)] in machine code before calling
82128211
// this helper, particularly if we want to handle polymorphism with frequently changing types.
82138212
Assert(EQUIVALENT_TYPE_CACHE_SIZE == 8);
82148213
Type** equivTypes = cache->types;
8214+
8215+
Type* refType = equivTypes[0];
8216+
if (refType == nullptr || refType->GetScriptContext() != type->GetScriptContext())
8217+
{
8218+
// We could have guard that was invalidated while sweeping and now we have type coming from
8219+
// different scriptContext. Make sure that it matches the scriptContext in cachedTypes.
8220+
// If not, return false because as mentioned above, we don't cache cross-context objects.
8221+
#if DBG
8222+
if (refType == nullptr)
8223+
{
8224+
for (int i = 1;i < EQUIVALENT_TYPE_CACHE_SIZE;i++)
8225+
{
8226+
AssertMsg(equivTypes[i] == nullptr, "In equiv typed caches, if first element is nullptr, all others should be nullptr");
8227+
}
8228+
}
8229+
#endif
8230+
return false;
8231+
}
8232+
82158233
if (type == equivTypes[0] || type == equivTypes[1] || type == equivTypes[2] || type == equivTypes[3] ||
82168234
type == equivTypes[4] || type == equivTypes[5] || type == equivTypes[6] || type == equivTypes[7])
82178235
{
8236+
#if DBG
8237+
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
8238+
{
8239+
if (guard->WasReincarnated())
8240+
{
8241+
Output::Print(_u("EquivObjTypeSpec: Guard 0x%p was reincarnated and working now \n"), guard);
8242+
Output::Flush();
8243+
}
8244+
}
8245+
#endif
82188246
guard->SetType(type);
82198247
return true;
82208248
}
@@ -8230,12 +8258,6 @@ namespace Js
82308258
// same prototype, any of the equivalent fixed properties will match. If any has been overwritten, the
82318259
// corresponding guard would have been invalidated and we would bail out (as above).
82328260

8233-
Type* refType = equivTypes[0];
8234-
if (refType == nullptr)
8235-
{
8236-
return false;
8237-
}
8238-
82398261
if (cache->IsLoadedFromProto() && type->GetPrototype() != refType->GetPrototype())
82408262
{
82418263
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
@@ -8292,38 +8314,56 @@ namespace Js
82928314
return false;
82938315
}
82948316

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)
8317+
int emptySlotIndex = -1;
8318+
for (int i = 0;i < EQUIVALENT_TYPE_CACHE_SIZE;i++)
82988319
{
8299-
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8300-
if (equivTypes[index] != nullptr)
8320+
if (equivTypes[i] == nullptr)
83018321
{
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)
8322+
emptySlotIndex = i;
8323+
break;
8324+
};
8325+
}
8326+
8327+
// We have some empty slots, let us use those first
8328+
if (emptySlotIndex != -1)
8329+
{
8330+
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
83118331
{
8312-
cache->nextEvictionVictim = 0;
8332+
Output::Print(_u("EquivObjTypeSpec: Saving type in unused slot of equiv types cache. \n"));
8333+
Output::Flush();
83138334
}
8335+
equivTypes[emptySlotIndex] = type;
83148336
}
83158337
else
83168338
{
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;
8339+
// CONSIDER (EquivObjTypeSpec): Invent some form of least recently used eviction scheme.
8340+
uintptr_t index = (reinterpret_cast<uintptr_t>(type) >> 4) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8341+
8342+
if (cache->nextEvictionVictim == EQUIVALENT_TYPE_CACHE_SIZE)
8343+
{
8344+
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
8345+
// If nextEvictionVictim was never set, set it to next element after index
8346+
cache->nextEvictionVictim = (index + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8347+
}
8348+
else
8349+
{
8350+
Assert(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
8351+
__analysis_assume(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
8352+
equivTypes[cache->nextEvictionVictim] = equivTypes[index];
8353+
// Else, set it to next element after current nextEvictionVictim index
8354+
cache->nextEvictionVictim = (cache->nextEvictionVictim + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
8355+
}
83268356

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

0 commit comments

Comments
 (0)