blob: 97e4b636637a2e39523c4835e803caa923ecbad7 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "Backend.h"
using namespace Js;
#if ENABLE_NATIVE_CODEGEN
void * JavascriptNativeOperators::Op_SwitchStringLookUp(JavascriptString* str, Js::BranchDictionaryWrapper<JavascriptString*>* branchTargets, uintptr_t funcStart, uintptr_t funcEnd)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_SwitchStringLookUp);
void* defaultTarget = branchTargets->defaultTarget;
Js::BranchDictionaryWrapper<JavascriptString*>::BranchDictionary& stringDictionary = branchTargets->dictionary;
void* target = stringDictionary.Lookup(str, defaultTarget);
uintptr_t utarget = (uintptr_t)target;
AssertOrFailFastMsg((utarget - funcStart) <= (funcEnd - funcStart), "Switch string dictionary jump target outside of function");
return target;
JIT_HELPER_END(Op_SwitchStringLookUp);
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
void JavascriptNativeOperators::TracePropertyEquivalenceCheck(const JitEquivalentTypeGuard* guard, const Type* type, const Type* refType, bool isEquivalent, uint failedPropertyIndex)
{
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
{
uint propertyCount = guard->GetCache()->record.propertyCount;
Output::Print(_u("EquivObjTypeSpec: checking %u properties on operation %u, (type = 0x%p, ref type = 0x%p):\n"),
propertyCount, guard->GetObjTypeSpecFldId(), type, refType);
const Js::TypeEquivalenceRecord& record = guard->GetCache()->record;
ScriptContext* scriptContext = type->GetScriptContext();
if (isEquivalent)
{
if (Js::Configuration::Global.flags.Verbose)
{
Output::Print(_u(" <start>, "));
for (uint pi = 0; pi < propertyCount; pi++)
{
const EquivalentPropertyEntry* refInfo = &record.properties[pi];
const PropertyRecord* propertyRecord = scriptContext->GetPropertyName(refInfo->propertyId);
Output::Print(_u("%s(#%d)@%ua%dw%d, "), propertyRecord->GetBuffer(), propertyRecord->GetPropertyId(), refInfo->slotIndex, refInfo->isAuxSlot, refInfo->mustBeWritable);
}
Output::Print(_u("<end>\n"));
}
}
else
{
const EquivalentPropertyEntry* refInfo = &record.properties[failedPropertyIndex];
Js::PropertyEquivalenceInfo info(Constants::NoSlot, false, false);
const PropertyRecord* propertyRecord = scriptContext->GetPropertyName(refInfo->propertyId);
if (DynamicType::Is(type))
{
Js::DynamicTypeHandler* typeHandler = (static_cast<const DynamicType*>(type))->GetTypeHandler();
typeHandler->GetPropertyEquivalenceInfo(propertyRecord, info);
}
Output::Print(_u("EquivObjTypeSpec: check failed for %s (#%d) on operation %u:\n"),
propertyRecord->GetBuffer(), propertyRecord->GetPropertyId(), guard->GetObjTypeSpecFldId());
Output::Print(_u(" type = 0x%p, ref type = 0x%p, slot = 0x%u (%d), ref slot = 0x%u (%d), is writable = %d, required writable = %d\n"),
type, refType, info.slotIndex, refInfo->slotIndex, info.isAuxSlot, refInfo->isAuxSlot, info.isWritable, refInfo->mustBeWritable);
}
Output::Flush();
}
}
#endif
bool JavascriptNativeOperators::IsStaticTypeObjTypeSpecEquivalent(const Js::TypeEquivalenceRecord& equivalenceRecord, uint& failedIndex)
{
uint propertyCount = equivalenceRecord.propertyCount;
Js::EquivalentPropertyEntry* properties = equivalenceRecord.properties;
for (uint pi = 0; pi < propertyCount; pi++)
{
const EquivalentPropertyEntry* refInfo = &properties[pi];
if (!IsStaticTypeObjTypeSpecEquivalent(refInfo))
{
failedIndex = pi;
return false;
}
}
return true;
}
bool JavascriptNativeOperators::IsStaticTypeObjTypeSpecEquivalent(const EquivalentPropertyEntry *entry)
{
// Objects of static types have no local properties, but they may load fields from their prototypes.
return entry->slotIndex == Constants::NoSlot && !entry->mustBeWritable;
}
bool JavascriptNativeOperators::CheckIfTypeIsEquivalentForFixedField(Type* type, JitEquivalentTypeGuard* guard)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(CheckIfTypeIsEquivalentForFixedField);
JIT_HELPER_SAME_ATTRIBUTES(CheckIfTypeIsEquivalentForFixedField, CheckIfTypeIsEquivalent);
if (guard->GetValue() == PropertyGuard::GuardValue::Invalidated_DuringSweep)
{
return false;
}
return CheckIfTypeIsEquivalent(type, guard);
JIT_HELPER_END(CheckIfTypeIsEquivalentForFixedField);
}
bool JavascriptNativeOperators::CheckIfPolyTypeIsEquivalentForFixedField(Type* type, JitPolyEquivalentTypeGuard* guard, uint8 index)
{
JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(CheckIfPolyTypeIsEquivalentForFixedField);
JIT_HELPER_SAME_ATTRIBUTES(CheckIfPolyTypeIsEquivalentForFixedField, CheckIfPolyTypeIsEquivalent);
if (guard->GetPolyValue(index) == PropertyGuard::GuardValue::Invalidated_DuringSweep)
{
return false;
}
return CheckIfPolyTypeIsEquivalent(type, guard, index);
JIT_HELPER_END(CheckIfPolyTypeIsEquivalentForFixedField);
}
bool JavascriptNativeOperators::CheckIfTypeIsEquivalent(Type* type, JitEquivalentTypeGuard* guard)
{
JIT_HELPER_NOT_REENTRANT_HEADER(CheckIfTypeIsEquivalent, reentrancylock, type->GetScriptContext()->GetThreadContext());
bool result = EquivalenceCheckHelper(type, guard, guard->GetValue());
if (result)
{
guard->SetTypeAddr((intptr_t)type);
}
return result;
JIT_HELPER_END(CheckIfTypeIsEquivalent);
}
bool JavascriptNativeOperators::CheckIfPolyTypeIsEquivalent(Type* type, JitPolyEquivalentTypeGuard* guard, uint8 index)
{
JIT_HELPER_NOT_REENTRANT_HEADER(CheckIfPolyTypeIsEquivalent, reentrancylock, type->GetScriptContext()->GetThreadContext());
bool result = EquivalenceCheckHelper(type, guard, guard->GetPolyValue(index));
if (result)
{
guard->SetPolyValue((intptr_t)type, index);
}
return result;
JIT_HELPER_END(CheckIfPolyTypeIsEquivalent);
}
bool JavascriptNativeOperators::EquivalenceCheckHelper(Type* type, JitEquivalentTypeGuard* guard, intptr_t guardValue)
{
if (guardValue == PropertyGuard::GuardValue::Invalidated)
{
return false;
}
AssertMsg(type && type->GetScriptContext(), "type and its ScriptContext should be valid.");
if (guardValue != PropertyGuard::GuardValue::Invalidated_DuringSweep &&
guardValue != PropertyGuard::GuardValue::Uninitialized &&
((Js::Type*)guardValue)->GetScriptContext() != type->GetScriptContext())
{
// For valid guard value, can't cache cross-context objects
return false;
}
// CONSIDER : Add stats on how often the cache hits, and simply force bailout if
// the efficacy is too low.
EquivalentTypeCache* cache = guard->GetCache();
// CONSIDER : Consider emitting o.type == equivTypes[hash(o.type)] in machine code before calling
// this helper, particularly if we want to handle polymorphism with frequently changing types.
Assert(EQUIVALENT_TYPE_CACHE_SIZE == 8);
Type** equivTypes = cache->types;
Type* refType = equivTypes[0];
if (refType == nullptr || refType->GetScriptContext() != type->GetScriptContext())
{
// We could have guard that was invalidated while sweeping and now we have type coming from
// different scriptContext. Make sure that it matches the scriptContext in cachedTypes.
// If not, return false because as mentioned above, we don't cache cross-context objects.
#if DBG
if (refType == nullptr)
{
for (int i = 1; i < EQUIVALENT_TYPE_CACHE_SIZE; i++)
{
AssertMsg(equivTypes[i] == nullptr, "In equiv typed caches, if first element is nullptr, all others should be nullptr");
}
}
#endif
return false;
}
int emptySlotIndex = -1;
for (int i = 0; i < EQUIVALENT_TYPE_CACHE_SIZE; i++)
{
if (equivTypes[i] == type)
{
#if DBG
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
{
if (guard->WasReincarnated())
{
Output::Print(_u("EquivObjTypeSpec: Guard 0x%p was reincarnated and working now \n"), guard);
Output::Flush();
}
}
#endif
return true;
}
if (equivTypes[i] == nullptr)
{
emptySlotIndex = i;
break;
}
}
// If we didn't find the type in the cache, let's check if it's equivalent the slow way, by comparing
// each of its relevant property slots to its equivalent in one of the cached types.
// We are making a few assumption that simplify the process:
// 1. If two types have the same prototype, any properties loaded from a prototype must come from the same slot.
// If any of the prototypes in the chain was altered such that this is no longer true, the corresponding
// property guard would have been invalidated and we would bail out at the guard check (either on this
// type check or downstream, but before the property load is attempted).
// 2. For polymorphic field loads fixed fields are only supported on prototypes. Hence, if two types have the
// same prototype, any of the equivalent fixed properties will match. If any has been overwritten, the
// corresponding guard would have been invalidated and we would bail out (as above).
if (cache->IsLoadedFromProto() && type->GetPrototype() != refType->GetPrototype())
{
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
{
Output::Print(_u("EquivObjTypeSpec: failed check on operation %u (type = 0x%x, ref type = 0x%x, proto = 0x%x, ref proto = 0x%x) \n"),
guard->GetObjTypeSpecFldId(), type, refType, type->GetPrototype(), refType->GetPrototype());
Output::Flush();
}
return false;
}
#pragma prefast(suppress:6011) // If type is nullptr, we would AV at the beginning of this method
if (type->GetTypeId() != refType->GetTypeId())
{
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
{
Output::Print(_u("EquivObjTypeSpec: failed check on operation %u (type = 0x%x, ref type = 0x%x, proto = 0x%x, ref proto = 0x%x) \n"),
guard->GetObjTypeSpecFldId(), type, refType, type->GetPrototype(), refType->GetPrototype());
Output::Flush();
}
return false;
}
// Review : This is quite slow. We could make it somewhat faster, by keeping slot indexes instead
// of property IDs, but that would mean we would need to look up property IDs from slot indexes when installing
// property guards, or maintain a whole separate list of equivalent slot indexes.
Assert(cache->record.propertyCount > 0);
// Before checking for equivalence, track existing cached non-shared types
DynamicType * dynamicType = (type && DynamicType::Is(type)) ? static_cast<DynamicType*>(type) : nullptr;
bool isEquivTypesCacheFull = emptySlotIndex == -1;
int nonSharedTypeSlotIndex = -1;
if (isEquivTypesCacheFull && dynamicType != nullptr)
{
for (int i = 0; i < EQUIVALENT_TYPE_CACHE_SIZE; i++)
{
// Track presence of cached non-shared type if cache is full
if (DynamicType::Is(equivTypes[i]) &&
!(static_cast<DynamicType*>(equivTypes[i]))->GetIsShared())
{
nonSharedTypeSlotIndex = i;
break;
}
}
// If cache is full, then this is definitely a sharedType, so evict non-shared type.
// Else evict next empty slot (only applicable for DynamicTypes)
emptySlotIndex = nonSharedTypeSlotIndex;
}
// If we get non-shared type while cache is full and we don't have any non-shared type to evict
// consider this type as non-equivalent
if (dynamicType != nullptr &&
isEquivTypesCacheFull &&
!dynamicType->GetIsShared() &&
nonSharedTypeSlotIndex == -1)
{
return false;
}
// CONSIDER (EquivObjTypeSpec): Impose a limit on the number of properties guarded by an equivalent type check.
// The trick is where in the glob opt to make the cut off. Perhaps in the forward pass we could track the number of
// field operations protected by a type check (keep a counter on the type's value info), and if that counter exceeds
// some threshold, simply stop optimizing any further instructions.
bool isEquivalent;
uint failedPropertyIndex;
if (dynamicType != nullptr)
{
Js::DynamicTypeHandler* typeHandler = dynamicType->GetTypeHandler();
isEquivalent = typeHandler->IsObjTypeSpecEquivalent(type, cache->record, failedPropertyIndex);
}
else
{
Assert(StaticType::Is(type->GetTypeId()));
isEquivalent = IsStaticTypeObjTypeSpecEquivalent(cache->record, failedPropertyIndex);
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
TracePropertyEquivalenceCheck(guard, type, refType, isEquivalent, failedPropertyIndex);
#endif
if (!isEquivalent)
{
return false;
}
AssertMsg(!isEquivTypesCacheFull || !dynamicType || dynamicType->GetIsShared() || nonSharedTypeSlotIndex > -1, "If equiv cache is full, then this should be sharedType or we will evict non-shared type.");
// We have some empty slots, let us use those first
if (emptySlotIndex != -1)
{
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
{
Output::Print(_u("EquivObjTypeSpec: Saving type in unused slot of equiv types cache. \n"));
Output::Flush();
}
equivTypes[emptySlotIndex] = type;
}
else
{
// CONSIDER (EquivObjTypeSpec): Invent some form of least recently used eviction scheme.
uintptr_t index = (reinterpret_cast<uintptr_t>(type) >> 4) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
if (cache->nextEvictionVictim == EQUIVALENT_TYPE_CACHE_SIZE)
{
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
// If nextEvictionVictim was never set, set it to next element after index
cache->nextEvictionVictim = (index + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
}
else
{
Assert(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
__analysis_assume(cache->nextEvictionVictim < EQUIVALENT_TYPE_CACHE_SIZE);
equivTypes[cache->nextEvictionVictim] = equivTypes[index];
// Else, set it to next element after current nextEvictionVictim index
cache->nextEvictionVictim = (cache->nextEvictionVictim + 1) & (EQUIVALENT_TYPE_CACHE_SIZE - 1);
}
if (PHASE_TRACE1(Js::EquivObjTypeSpecPhase))
{
Output::Print(_u("EquivObjTypeSpec: Saving type in used slot of equiv types cache at index = %d. NextEvictionVictim = %d. \n"), index, cache->nextEvictionVictim);
Output::Flush();
}
Assert(index < EQUIVALENT_TYPE_CACHE_SIZE);
__analysis_assume(index < EQUIVALENT_TYPE_CACHE_SIZE);
equivTypes[index] = type;
}
// Fixed field checks allow us to assume a specific type ID, but the assumption is only
// valid if we lock the type. Otherwise, the type ID may change out from under us without
// evolving the type.
// We also need to lock the type in case of, for instance, adding a property to a dictionary type handler.
if (dynamicType != nullptr)
{
if (!dynamicType->GetIsLocked())
{
// We only need to lock the type to prevent against the type evolving after it has been cached. If the type becomes shared
// in the future, any further changes to the type will result in creating a new type handler.
dynamicType->LockTypeOnly();
}
}
type->SetHasBeenCached();
return true;
}
#endif
Var JavascriptNativeOperators::OP_GetElementI_JIT_ExpectingNativeFloatArray(Var instance, Var index, ScriptContext *scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetElementI_ExpectingNativeFloatArray);
Assert(Js::JavascriptStackWalker::ValidateTopJitFrame(scriptContext));
JavascriptOperators::UpdateNativeArrayProfileInfoToCreateVarArray(instance, true, false);
return JavascriptOperators::OP_GetElementI_JIT(instance, index, scriptContext);
JIT_HELPER_END(Op_GetElementI_ExpectingNativeFloatArray);
}
Var JavascriptNativeOperators::OP_GetElementI_JIT_ExpectingVarArray(Var instance, Var index, ScriptContext *scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetElementI_ExpectingVarArray);
Assert(Js::JavascriptStackWalker::ValidateTopJitFrame(scriptContext));
JavascriptOperators::UpdateNativeArrayProfileInfoToCreateVarArray(instance, false, true);
return JavascriptOperators::OP_GetElementI_JIT(instance, index, scriptContext);
JIT_HELPER_END(Op_GetElementI_ExpectingVarArray);
}
Var JavascriptNativeOperators::OP_GetElementI_UInt32_ExpectingNativeFloatArray(Var instance, uint32 index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetElementI_UInt32_ExpectingNativeFloatArray);
#if ENABLE_PROFILE_INFO
JavascriptOperators::UpdateNativeArrayProfileInfoToCreateVarArray(instance, true, false);
#endif
return JavascriptOperators::OP_GetElementI_UInt32(instance, index, scriptContext);
JIT_HELPER_END(Op_GetElementI_UInt32_ExpectingNativeFloatArray);
}
Var JavascriptNativeOperators::OP_GetElementI_UInt32_ExpectingVarArray(Var instance, uint32 index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetElementI_UInt32_ExpectingVarArray);
#if ENABLE_PROFILE_INFO
JavascriptOperators::UpdateNativeArrayProfileInfoToCreateVarArray(instance, false, true);
#endif
return JavascriptOperators::OP_GetElementI_UInt32(instance, index, scriptContext);
JIT_HELPER_END(Op_GetElementI_UInt32_ExpectingVarArray);
}
Var JavascriptNativeOperators::OP_GetElementI_Int32_ExpectingNativeFloatArray(Var instance, int32 index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetElementI_Int32_ExpectingNativeFloatArray);
#if ENABLE_PROFILE_INFO
JavascriptOperators::UpdateNativeArrayProfileInfoToCreateVarArray(instance, true, false);
#endif
return JavascriptOperators::OP_GetElementI_Int32(instance, index, scriptContext);
JIT_HELPER_END(Op_GetElementI_Int32_ExpectingNativeFloatArray);
}
Var JavascriptNativeOperators::OP_GetElementI_Int32_ExpectingVarArray(Var instance, int32 index, ScriptContext* scriptContext)
{
JIT_HELPER_REENTRANT_HEADER(Op_GetElementI_Int32_ExpectingVarArray);
#if ENABLE_PROFILE_INFO
JavascriptOperators::UpdateNativeArrayProfileInfoToCreateVarArray(instance, false, true);
#endif
return JavascriptOperators::OP_GetElementI_Int32(instance, index, scriptContext);
JIT_HELPER_END(Op_GetElementI_Int32_ExpectingVarArray);
}
#if DBG
void JavascriptNativeOperators::IntRangeCheckFailure()
{
AssertOrFailFastMsg(false, "Int range determined by the GlobOpt invalid at runtime");
}
#endif