blob: 818ec471b85d1d36b19b71cebd1506a350fc149f [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeLibraryPch.h"
namespace Js
{
ScriptFunctionBase::ScriptFunctionBase(DynamicType * type) :
JavascriptFunction(type)
{}
ScriptFunctionBase::ScriptFunctionBase(DynamicType * type, FunctionInfo * functionInfo) :
JavascriptFunction(type, functionInfo)
{}
bool ScriptFunctionBase::Is(Var func)
{
return ScriptFunction::Is(func) || JavascriptGeneratorFunction::Is(func) || JavascriptAsyncFunction::Is(func);
}
ScriptFunctionBase * ScriptFunctionBase::FromVar(Var func)
{
Assert(ScriptFunctionBase::Is(func));
return reinterpret_cast<ScriptFunctionBase *>(func);
}
ScriptFunction::ScriptFunction(DynamicType * type) :
ScriptFunctionBase(type), environment((FrameDisplay*)&NullFrameDisplay),
cachedScopeObj(nullptr), hasInlineCaches(false), hasSuperReference(false), homeObj(nullptr),
computedNameVar(nullptr), isActiveScript(false)
{}
ScriptFunction::ScriptFunction(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType)
: ScriptFunctionBase(deferredPrototypeType, proxy->GetFunctionInfo()),
environment((FrameDisplay*)&NullFrameDisplay), cachedScopeObj(nullptr), homeObj(nullptr),
hasInlineCaches(false), hasSuperReference(false), isActiveScript(false), computedNameVar(nullptr)
{
Assert(proxy->GetFunctionInfo()->GetFunctionProxy() == proxy);
Assert(proxy->EnsureDeferredPrototypeType() == deferredPrototypeType);
DebugOnly(VerifyEntryPoint());
#if ENABLE_NATIVE_CODEGEN
#ifdef BGJIT_STATS
if (!proxy->IsDeferred())
{
FunctionBody* body = proxy->GetFunctionBody();
if(!body->GetNativeEntryPointUsed() &&
body->GetDefaultFunctionEntryPointInfo()->IsCodeGenDone())
{
MemoryBarrier();
type->GetScriptContext()->jitCodeUsed += body->GetByteCodeCount();
type->GetScriptContext()->funcJitCodeUsed++;
body->SetNativeEntryPointUsed(true);
}
}
#endif
#endif
}
ScriptFunction * ScriptFunction::OP_NewScFunc(FrameDisplay *environment, FunctionInfoPtrPtr infoRef)
{
AssertMsg(infoRef!= nullptr, "BYTE-CODE VERIFY: Must specify a valid function to create");
FunctionProxy* functionProxy = (*infoRef)->GetFunctionProxy();
AssertMsg(functionProxy!= nullptr, "BYTE-CODE VERIFY: Must specify a valid function to create");
ScriptContext* scriptContext = functionProxy->GetScriptContext();
bool hasSuperReference = functionProxy->HasSuperReference();
if (functionProxy->IsFunctionBody() && functionProxy->GetFunctionBody()->GetInlineCachesOnFunctionObject())
{
Js::FunctionBody * functionBody = functionProxy->GetFunctionBody();
ScriptFunctionWithInlineCache* pfuncScriptWithInlineCache = scriptContext->GetLibrary()->CreateScriptFunctionWithInlineCache(functionProxy);
pfuncScriptWithInlineCache->SetEnvironment(environment);
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(pfuncScriptWithInlineCache, EtwTrace::GetFunctionId(functionProxy)));
Assert(functionBody->GetInlineCacheCount() + functionBody->GetIsInstInlineCacheCount());
if (functionBody->GetIsFirstFunctionObject())
{
// point the inline caches of the first function object to those on the function body.
pfuncScriptWithInlineCache->SetInlineCachesFromFunctionBody();
functionBody->SetIsNotFirstFunctionObject();
}
else
{
// allocate inline cache for this function object
pfuncScriptWithInlineCache->CreateInlineCache();
}
pfuncScriptWithInlineCache->SetHasSuperReference(hasSuperReference);
if (PHASE_TRACE1(Js::ScriptFunctionWithInlineCachePhase))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Function object with inline cache: function number: (%s)\tfunction name: %s\n"),
functionBody->GetDebugNumberSet(debugStringBuffer), functionBody->GetDisplayName());
Output::Flush();
}
return pfuncScriptWithInlineCache;
}
else if(functionProxy->IsFunctionBody() && functionProxy->GetFunctionBody()->GetIsAsmJsFunction())
{
AsmJsScriptFunction* asmJsFunc = scriptContext->GetLibrary()->CreateAsmJsScriptFunction(functionProxy);
asmJsFunc->SetEnvironment(environment);
Assert(!hasSuperReference);
asmJsFunc->SetHasSuperReference(hasSuperReference);
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(asmJsFunc, EtwTrace::GetFunctionId(functionProxy)));
return asmJsFunc;
}
else
{
ScriptFunction* pfuncScript = scriptContext->GetLibrary()->CreateScriptFunction(functionProxy);
pfuncScript->SetEnvironment(environment);
pfuncScript->SetHasSuperReference(hasSuperReference);
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(pfuncScript, EtwTrace::GetFunctionId(functionProxy)));
return pfuncScript;
}
}
void ScriptFunction::SetEnvironment(FrameDisplay * environment)
{
//Assert(ThreadContext::IsOnStack(this) || !ThreadContext::IsOnStack(environment));
this->environment = environment;
}
void ScriptFunction::InvalidateCachedScopeChain()
{
// Note: Currently this helper assumes that we're in an eval-class case
// where all the contents of the closure environment are dynamic objects.
// Invalidating scopes that are raw slot arrays, etc., will have to be done
// directly in byte code.
// A function nested within this one has escaped.
// Invalidate our own cached scope object, and walk the closure environment
// doing this same.
if (this->cachedScopeObj)
{
this->cachedScopeObj->InvalidateCachedScope();
}
FrameDisplay *pDisplay = this->environment;
uint length = (uint)pDisplay->GetLength();
for (uint i = 0; i < length; i++)
{
Var scope = pDisplay->GetItem(i);
RecyclableObject *scopeObj = RecyclableObject::FromVar(scope);
scopeObj->InvalidateCachedScope();
}
}
bool ScriptFunction::Is(Var func)
{
return JavascriptFunction::Is(func) && JavascriptFunction::FromVar(func)->GetFunctionInfo()->HasBody();
}
ScriptFunction * ScriptFunction::FromVar(Var func)
{
Assert(ScriptFunction::Is(func));
return reinterpret_cast<ScriptFunction *>(func);
}
ProxyEntryPointInfo * ScriptFunction::GetEntryPointInfo() const
{
return this->GetScriptFunctionType()->GetEntryPointInfo();
}
ScriptFunctionType * ScriptFunction::GetScriptFunctionType() const
{
return (ScriptFunctionType *)GetDynamicType();
}
ScriptFunctionType * ScriptFunction::DuplicateType()
{
ScriptFunctionType* type = RecyclerNew(this->GetScriptContext()->GetRecycler(),
ScriptFunctionType, this->GetScriptFunctionType());
this->GetFunctionProxy()->RegisterFunctionObjectType(type);
return type;
}
uint32 ScriptFunction::GetFrameHeight(FunctionEntryPointInfo* entryPointInfo) const
{
Assert(this->GetFunctionBody() != nullptr);
return this->GetFunctionBody()->GetFrameHeight(entryPointInfo);
}
bool ScriptFunction::HasFunctionBody()
{
// for asmjs we want to first check if the FunctionObject has a function body. Check that the function is not deferred
return !this->GetFunctionInfo()->IsDeferredParseFunction() && !this->GetFunctionInfo()->IsDeferredDeserializeFunction() && GetParseableFunctionInfo()->IsFunctionParsed();
}
void ScriptFunction::ChangeEntryPoint(ProxyEntryPointInfo* entryPointInfo, JavascriptMethod entryPoint)
{
Assert(entryPoint != nullptr);
Assert(this->GetTypeId() == TypeIds_Function);
#if ENABLE_NATIVE_CODEGEN
Assert(!IsCrossSiteObject() || entryPoint != (Js::JavascriptMethod)checkCodeGenThunk);
#else
Assert(!IsCrossSiteObject());
#endif
Assert((entryPointInfo != nullptr && this->GetFunctionProxy() != nullptr));
if (this->GetEntryPoint() == entryPoint && this->GetScriptFunctionType()->GetEntryPointInfo() == entryPointInfo)
{
return;
}
bool isAsmJS = false;
if (HasFunctionBody())
{
isAsmJS = this->GetFunctionBody()->GetIsAsmjsMode();
}
// ASMJS:- for asmjs we don't need to update the entry point here as it updates the types entry point
if (!isAsmJS)
{
// We can't go from cross-site to non-cross-site. Update only in the non-cross site case
if (!CrossSite::IsThunk(this->GetEntryPoint()))
{
this->SetEntryPoint(entryPoint);
}
}
// instead update the address in the function entrypoint info
else
{
entryPointInfo->jsMethod = entryPoint;
}
if (!isAsmJS)
{
ProxyEntryPointInfo* oldEntryPointInfo = this->GetScriptFunctionType()->GetEntryPointInfo();
if (oldEntryPointInfo
&& oldEntryPointInfo != entryPointInfo
&& oldEntryPointInfo->SupportsExpiration())
{
// The old entry point could be executing so we need root it to make sure
// it isn't prematurely collected. The rooting is done by queuing it up on the threadContext
ThreadContext* threadContext = ThreadContext::GetContextForCurrentThread();
threadContext->QueueFreeOldEntryPointInfoIfInScript((FunctionEntryPointInfo*)oldEntryPointInfo);
}
}
this->GetScriptFunctionType()->SetEntryPointInfo(entryPointInfo);
}
FunctionProxy * ScriptFunction::GetFunctionProxy() const
{
Assert(this->functionInfo->HasBody());
return this->functionInfo->GetFunctionProxy();
}
JavascriptMethod ScriptFunction::UpdateUndeferredBody(FunctionBody* newFunctionInfo)
{
// Update deferred parsed/serialized function to the real function body
Assert(this->functionInfo->HasBody());
Assert(this->functionInfo->GetFunctionBody() == newFunctionInfo);
Assert(!newFunctionInfo->IsDeferred());
DynamicType * type = this->GetDynamicType();
// If the type is shared, it must be the shared one in the old function proxy
this->functionInfo = newFunctionInfo->GetFunctionInfo();
if (type->GetIsShared())
{
// the type is still shared, we can't modify it, just migrate to the shared one in the function body
this->ReplaceType(newFunctionInfo->EnsureDeferredPrototypeType());
}
// The type has change from the default, it is not share, just use that one.
JavascriptMethod directEntryPoint = newFunctionInfo->GetDirectEntryPoint(newFunctionInfo->GetDefaultEntryPointInfo());
#if defined(ENABLE_SCRIPT_PROFILING) || defined(ENABLE_SCRIPT_DEBUGGING)
Assert(directEntryPoint != DefaultDeferredParsingThunk
&& directEntryPoint != ProfileDeferredParsingThunk);
#else
Assert(directEntryPoint != DefaultDeferredParsingThunk);
#endif
Js::FunctionEntryPointInfo* defaultEntryPointInfo = newFunctionInfo->GetDefaultFunctionEntryPointInfo();
JavascriptMethod thunkEntryPoint = this->UpdateThunkEntryPoint(defaultEntryPointInfo, directEntryPoint);
this->GetScriptFunctionType()->SetEntryPointInfo(defaultEntryPointInfo);
return thunkEntryPoint;
}
JavascriptMethod ScriptFunction::UpdateThunkEntryPoint(FunctionEntryPointInfo* entryPointInfo, JavascriptMethod entryPoint)
{
this->ChangeEntryPoint(entryPointInfo, entryPoint);
if (!CrossSite::IsThunk(this->GetEntryPoint()))
{
return entryPoint;
}
// We already pass through the cross site thunk, which would have called the profile thunk already if necessary
// So just call the original entry point if our direct entry is the profile entry thunk
// Otherwise, call the directEntryPoint which may have additional processing to do (e.g. ensure dynamic profile)
Assert(this->IsCrossSiteObject());
if (entryPoint != ProfileEntryThunk)
{
return entryPoint;
}
// Based on the comment below, this shouldn't be a defer deserialization function as it would have a deferred thunk
FunctionBody * functionBody = this->GetFunctionBody();
// The original entry point should be an interpreter thunk or the native entry point;
Assert(functionBody->IsInterpreterThunk() || functionBody->IsNativeOriginalEntryPoint());
return functionBody->GetOriginalEntryPoint();
}
bool ScriptFunction::IsNewEntryPointAvailable()
{
Js::FunctionEntryPointInfo *const defaultEntryPointInfo = this->GetFunctionBody()->GetDefaultFunctionEntryPointInfo();
JavascriptMethod defaultEntryPoint = this->GetFunctionBody()->GetDirectEntryPoint(defaultEntryPointInfo);
return this->GetEntryPoint() != defaultEntryPoint;
}
Var ScriptFunction::GetSourceString() const
{
return this->GetFunctionProxy()->EnsureDeserialized()->GetCachedSourceString();
}
Var ScriptFunction::FormatToString(JavascriptString* inputString)
{
FunctionProxy* proxy = this->GetFunctionProxy();
ParseableFunctionInfo * pFuncBody = proxy->EnsureDeserialized();
const char16 * inputStr = inputString->GetString();
const char16 * paramStr = wcschr(inputStr, _u('('));
if (paramStr == nullptr || wcscmp(pFuncBody->GetDisplayName(), Js::Constants::EvalCode) == 0)
{
Assert(pFuncBody->IsEval());
return inputString;
}
ScriptContext* scriptContext = this->GetScriptContext();
JavascriptLibrary* library = scriptContext->GetLibrary();
bool isClassMethod = this->GetFunctionInfo()->IsClassMethod() || this->GetFunctionInfo()->IsClassConstructor();
JavascriptString* prefixString = nullptr;
uint prefixStringLength = 0;
const char16* name = _u("");
charcount_t nameLength = 0;
Var returnStr = nullptr;
if (!isClassMethod)
{
prefixString = library->GetFunctionPrefixString();
if (pFuncBody->IsGenerator())
{
prefixString = library->GetGeneratorFunctionPrefixString();
}
else if (pFuncBody->IsAsync())
{
prefixString = library->GetAsyncFunctionPrefixString();
}
prefixStringLength = prefixString->GetLength();
if (pFuncBody->GetIsAccessor())
{
name = pFuncBody->GetShortDisplayName(&nameLength);
}
else if (pFuncBody->GetIsDeclaration() || pFuncBody->GetIsNamedFunctionExpression())
{
name = pFuncBody->GetDisplayName();
nameLength = pFuncBody->GetDisplayNameLength();
if (name == Js::Constants::FunctionCode)
{
name = Js::Constants::Anonymous;
nameLength = Js::Constants::AnonymousLength;
}
}
}
else
{
if (this->GetFunctionInfo()->IsClassConstructor())
{
name = _u("constructor");
nameLength = _countof(_u("constructor")) -1; //subtract off \0
}
else
{
name = pFuncBody->GetShortDisplayName(&nameLength); //strip off prototype.
}
}
ENTER_PINNED_SCOPE(JavascriptString, computedName);
computedName = this->GetComputedName();
if (computedName != nullptr)
{
prefixString = nullptr;
prefixStringLength = 0;
name = computedName->GetString();
nameLength = computedName->GetLength();
}
uint functionBodyLength = inputString->GetLength() - ((uint)(paramStr - inputStr));
size_t totalLength = prefixStringLength + functionBodyLength + nameLength;
if (!IsValidCharCount(totalLength))
{
// We throw here because computed property names are evaluated at runtime and
// thus are not a subset string of function body source (parameter inputString).
// For all other cases totalLength <= inputString->GetLength().
JavascriptExceptionOperators::ThrowOutOfMemory(this->GetScriptContext());
}
char16 * funcBodyStr = RecyclerNewArrayLeaf(this->GetScriptContext()->GetRecycler(), char16, totalLength);
char16 * funcBodyStrStart = funcBodyStr;
if (prefixString != nullptr)
{
js_wmemcpy_s(funcBodyStr, prefixStringLength, prefixString->GetString(), prefixStringLength);
funcBodyStrStart += prefixStringLength;
}
js_wmemcpy_s(funcBodyStrStart, nameLength, name, nameLength);
funcBodyStrStart = funcBodyStrStart + nameLength;
js_wmemcpy_s(funcBodyStrStart, functionBodyLength, paramStr, functionBodyLength);
returnStr = LiteralString::NewCopyBuffer(funcBodyStr, (charcount_t)totalLength, scriptContext);
LEAVE_PINNED_SCOPE();
return returnStr;
}
Var ScriptFunction::EnsureSourceString()
{
// The function may be defer serialize, need to be deserialized
FunctionProxy* proxy = this->GetFunctionProxy();
ParseableFunctionInfo * pFuncBody = proxy->EnsureDeserialized();
Var cachedSourceString = pFuncBody->GetCachedSourceString();
if (cachedSourceString != nullptr)
{
return cachedSourceString;
}
ScriptContext * scriptContext = this->GetScriptContext();
//Library code should behave the same way as RuntimeFunctions
Utf8SourceInfo* source = pFuncBody->GetUtf8SourceInfo();
if ((source != nullptr && source->GetIsLibraryCode())
#ifdef ENABLE_WASM
|| (pFuncBody->IsWasmFunction())
#endif
)
{
//Don't display if it is anonymous function
charcount_t displayNameLength = 0;
PCWSTR displayName = pFuncBody->GetShortDisplayName(&displayNameLength);
cachedSourceString = JavascriptFunction::GetLibraryCodeDisplayString(scriptContext, displayName);
}
else if (!pFuncBody->GetUtf8SourceInfo()->GetIsXDomain()
// To avoid backward compat issue, we will not give out sourceString for function if it is called from
// window.onerror trying to retrieve arguments.callee.caller.
&& !(pFuncBody->GetUtf8SourceInfo()->GetIsXDomainString() && scriptContext->GetThreadContext()->HasUnhandledException())
)
{
// Decode UTF8 into Unicode
// Consider: Should we have a JavascriptUtf8Substring class which defers decoding
// until it's needed?
charcount_t cch = pFuncBody->LengthInChars();
size_t cbLength = pFuncBody->LengthInBytes();
LPCUTF8 pbStart = pFuncBody->GetSource(_u("ScriptFunction::EnsureSourceString"));
BufferStringBuilder builder(cch, scriptContext);
utf8::DecodeOptions options = pFuncBody->GetUtf8SourceInfo()->IsCesu8() ? utf8::doAllowThreeByteSurrogates : utf8::doDefault;
size_t decodedCount = utf8::DecodeUnitsInto(builder.DangerousGetWritableBuffer(), pbStart, pbStart + cbLength, options);
if (decodedCount != cch)
{
AssertMsg(false, "Decoded incorrect number of characters for function body");
Js::Throw::FatalInternalError();
}
if (pFuncBody->IsLambda() || isActiveScript || this->GetFunctionInfo()->IsClassConstructor()
#ifdef ENABLE_PROJECTION
|| scriptContext->GetConfig()->IsWinRTEnabled()
#endif
)
{
cachedSourceString = builder.ToString();
}
else
{
cachedSourceString = FormatToString(builder.ToString());
}
}
else
{
cachedSourceString = scriptContext->GetLibrary()->GetXDomainFunctionDisplayString();
}
Assert(cachedSourceString != nullptr);
pFuncBody->SetCachedSourceString(cachedSourceString);
return cachedSourceString;
}
#if ENABLE_TTD
void ScriptFunction::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
{
Js::FunctionBody* fb = TTD::JsSupport::ForceAndGetFunctionBody(this->GetParseableFunctionInfo());
extractor->MarkFunctionBody(fb);
Js::FrameDisplay* environment = this->GetEnvironment();
if(environment->GetLength() != 0)
{
extractor->MarkScriptFunctionScopeInfo(environment);
}
if(this->cachedScopeObj != nullptr)
{
extractor->MarkVisitVar(this->cachedScopeObj);
}
if(this->homeObj != nullptr)
{
extractor->MarkVisitVar(this->homeObj);
}
if(this->computedNameVar != nullptr)
{
extractor->MarkVisitVar(this->computedNameVar);
}
}
void ScriptFunction::ProcessCorePaths()
{
TTD::RuntimeContextInfo* rctxInfo = this->GetScriptContext()->TTDWellKnownInfo;
//do the body path mark
Js::FunctionBody* fb = TTD::JsSupport::ForceAndGetFunctionBody(this->GetParseableFunctionInfo());
rctxInfo->EnqueueNewFunctionBodyObject(this, fb, _u("!fbody"));
Js::FrameDisplay* environment = this->GetEnvironment();
uint32 scopeCount = environment->GetLength();
for(uint32 i = 0; i < scopeCount; ++i)
{
TTD::UtilSupport::TTAutoString scopePathString;
rctxInfo->BuildEnvironmentIndexBuffer(i, scopePathString);
void* scope = environment->GetItem(i);
switch(environment->GetScopeType(scope))
{
case Js::ScopeType::ScopeType_ActivationObject:
case Js::ScopeType::ScopeType_WithScope:
{
rctxInfo->EnqueueNewPathVarAsNeeded(this, (Js::Var)scope, scopePathString.GetStrValue());
break;
}
case Js::ScopeType::ScopeType_SlotArray:
{
Js::ScopeSlots slotArray = (Js::Var*)scope;
uint slotArrayCount = slotArray.GetCount();
//get the function body associated with the scope
if(slotArray.IsFunctionScopeSlotArray())
{
rctxInfo->EnqueueNewFunctionBodyObject(this, slotArray.GetFunctionInfo()->GetFunctionBody(), scopePathString.GetStrValue());
}
else
{
rctxInfo->AddWellKnownDebuggerScopePath(this, slotArray.GetDebuggerScope(), i);
}
for(uint j = 0; j < slotArrayCount; j++)
{
Js::Var sval = slotArray.Get(j);
TTD::UtilSupport::TTAutoString slotPathString;
rctxInfo->BuildEnvironmentIndexAndSlotBuffer(i, j, slotPathString);
rctxInfo->EnqueueNewPathVarAsNeeded(this, sval, slotPathString.GetStrValue());
}
break;
}
default:
TTDAssert(false, "Unknown scope kind");
}
}
if(this->cachedScopeObj != nullptr)
{
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->cachedScopeObj, _u("_cachedScopeObj"));
}
if(this->homeObj != nullptr)
{
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->homeObj, _u("_homeObj"));
}
}
TTD::NSSnapObjects::SnapObjectType ScriptFunction::GetSnapTag_TTD() const
{
return TTD::NSSnapObjects::SnapObjectType::SnapScriptFunctionObject;
}
void ScriptFunction::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
TTDAssert(this->GetFunctionInfo() != nullptr, "We are only doing this for functions with ParseableFunctionInfo.");
TTD::NSSnapObjects::SnapScriptFunctionInfo* ssfi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapScriptFunctionInfo>();
Js::FunctionBody* fb = TTD::JsSupport::ForceAndGetFunctionBody(this->GetParseableFunctionInfo());
alloc.CopyNullTermStringInto(fb->GetDisplayName(), ssfi->DebugFunctionName);
ssfi->BodyRefId = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(fb);
Js::FrameDisplay* environment = this->GetEnvironment();
ssfi->ScopeId = TTD_INVALID_PTR_ID;
if(environment->GetLength() != 0)
{
ssfi->ScopeId = TTD_CONVERT_SCOPE_TO_PTR_ID(environment);
}
ssfi->CachedScopeObjId = TTD_INVALID_PTR_ID;
if(this->cachedScopeObj != nullptr)
{
ssfi->CachedScopeObjId = TTD_CONVERT_VAR_TO_PTR_ID(this->cachedScopeObj);
}
ssfi->HomeObjId = TTD_INVALID_PTR_ID;
if(this->homeObj != nullptr)
{
ssfi->HomeObjId = TTD_CONVERT_VAR_TO_PTR_ID(this->homeObj);
}
ssfi->ComputedNameInfo = TTD_CONVERT_JSVAR_TO_TTDVAR(this->computedNameVar);
ssfi->HasSuperReference = this->hasSuperReference;
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapScriptFunctionInfo*, TTD::NSSnapObjects::SnapObjectType::SnapScriptFunctionObject>(objData, ssfi);
}
#endif
AsmJsScriptFunction::AsmJsScriptFunction(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType) :
ScriptFunction(proxy, deferredPrototypeType), m_moduleMemory(nullptr)
{}
AsmJsScriptFunction::AsmJsScriptFunction(DynamicType * type) :
ScriptFunction(type), m_moduleMemory(nullptr)
{}
bool AsmJsScriptFunction::Is(Var func)
{
return ScriptFunction::Is(func) &&
ScriptFunction::FromVar(func)->HasFunctionBody() &&
ScriptFunction::FromVar(func)->GetFunctionBody()->GetIsAsmJsFunction();
}
bool AsmJsScriptFunction::IsWasmScriptFunction(Var func)
{
return ScriptFunction::Is(func) &&
ScriptFunction::FromVar(func)->HasFunctionBody() &&
ScriptFunction::FromVar(func)->GetFunctionBody()->IsWasmFunction();
}
AsmJsScriptFunction* AsmJsScriptFunction::FromVar(Var func)
{
Assert(AsmJsScriptFunction::Is(func));
return reinterpret_cast<AsmJsScriptFunction *>(func);
}
ScriptFunctionWithInlineCache::ScriptFunctionWithInlineCache(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType) :
ScriptFunction(proxy, deferredPrototypeType), hasOwnInlineCaches(false)
{}
ScriptFunctionWithInlineCache::ScriptFunctionWithInlineCache(DynamicType * type) :
ScriptFunction(type), hasOwnInlineCaches(false)
{}
bool ScriptFunctionWithInlineCache::Is(Var func)
{
return ScriptFunction::Is(func) && ScriptFunction::FromVar(func)->GetHasInlineCaches();
}
ScriptFunctionWithInlineCache* ScriptFunctionWithInlineCache::FromVar(Var func)
{
Assert(ScriptFunctionWithInlineCache::Is(func));
return reinterpret_cast<ScriptFunctionWithInlineCache *>(func);
}
InlineCache * ScriptFunctionWithInlineCache::GetInlineCache(uint index)
{
Assert(this->m_inlineCaches != nullptr);
Assert(index < this->GetInlineCacheCount());
#if DBG
Assert(this->m_inlineCacheTypes[index] == InlineCacheTypeNone ||
this->m_inlineCacheTypes[index] == InlineCacheTypeInlineCache);
this->m_inlineCacheTypes[index] = InlineCacheTypeInlineCache;
#endif
return reinterpret_cast<InlineCache *>(PointerValue(this->m_inlineCaches[index]));
}
void ScriptFunctionWithInlineCache::SetInlineCachesFromFunctionBody()
{
SetHasInlineCaches(true);
Js::FunctionBody* functionBody = this->GetFunctionBody();
this->m_inlineCaches = functionBody->GetInlineCaches();
#if DBG
this->m_inlineCacheTypes = functionBody->GetInlineCacheTypes();
#endif
this->rootObjectLoadInlineCacheStart = functionBody->GetRootObjectLoadInlineCacheStart();
this->rootObjectLoadMethodInlineCacheStart = functionBody->GetRootObjectLoadMethodInlineCacheStart();
this->rootObjectStoreInlineCacheStart = functionBody->GetRootObjectStoreInlineCacheStart();
this->inlineCacheCount = functionBody->GetInlineCacheCount();
this->isInstInlineCacheCount = functionBody->GetIsInstInlineCacheCount();
}
void ScriptFunctionWithInlineCache::CreateInlineCache()
{
Js::FunctionBody *functionBody = this->GetFunctionBody();
this->rootObjectLoadInlineCacheStart = functionBody->GetRootObjectLoadInlineCacheStart();
this->rootObjectStoreInlineCacheStart = functionBody->GetRootObjectStoreInlineCacheStart();
this->inlineCacheCount = functionBody->GetInlineCacheCount();
this->isInstInlineCacheCount = functionBody->GetIsInstInlineCacheCount();
SetHasInlineCaches(true);
AllocateInlineCache();
hasOwnInlineCaches = true;
}
void ScriptFunctionWithInlineCache::Finalize(bool isShutdown)
{
if (isShutdown)
{
FreeOwnInlineCaches<true>();
}
else
{
FreeOwnInlineCaches<false>();
}
}
template<bool isShutdown>
void ScriptFunctionWithInlineCache::FreeOwnInlineCaches()
{
uint isInstInlineCacheStart = this->GetInlineCacheCount();
uint totalCacheCount = isInstInlineCacheStart + isInstInlineCacheCount;
if (this->GetHasInlineCaches() && this->m_inlineCaches && this->hasOwnInlineCaches)
{
Js::ScriptContext* scriptContext = this->GetParseableFunctionInfo()->GetScriptContext();
uint i = 0;
uint unregisteredInlineCacheCount = 0;
uint plainInlineCacheEnd = rootObjectLoadInlineCacheStart;
__analysis_assume(plainInlineCacheEnd < totalCacheCount);
for (; i < plainInlineCacheEnd; i++)
{
if (this->m_inlineCaches[i])
{
InlineCache* inlineCache = (InlineCache*)(void*)this->m_inlineCaches[i];
if (isShutdown)
{
inlineCache->Clear();
}
else if(!scriptContext->IsClosed())
{
if (inlineCache->RemoveFromInvalidationList())
{
unregisteredInlineCacheCount++;
}
AllocatorDelete(InlineCacheAllocator, scriptContext->GetInlineCacheAllocator(), inlineCache);
}
this->m_inlineCaches[i] = nullptr;
}
}
i = isInstInlineCacheStart;
for (; i < totalCacheCount; i++)
{
if (this->m_inlineCaches[i])
{
if (isShutdown)
{
((IsInstInlineCache*)this->m_inlineCaches[i])->Clear();
}
else if (!scriptContext->IsClosed())
{
AllocatorDelete(CacheAllocator, scriptContext->GetIsInstInlineCacheAllocator(), (IsInstInlineCache*)(void*)this->m_inlineCaches[i]);
}
this->m_inlineCaches[i] = nullptr;
}
}
if (unregisteredInlineCacheCount > 0)
{
AssertMsg(!isShutdown && !scriptContext->IsClosed(), "Unregistration of inlineCache should only be done if this is not shutdown or scriptContext closing.");
scriptContext->GetThreadContext()->NotifyInlineCacheBatchUnregistered(unregisteredInlineCacheCount);
}
}
}
void ScriptFunctionWithInlineCache::AllocateInlineCache()
{
Assert(this->m_inlineCaches == nullptr);
uint isInstInlineCacheStart = this->GetInlineCacheCount();
uint totalCacheCount = isInstInlineCacheStart + isInstInlineCacheCount;
Js::FunctionBody* functionBody = this->GetFunctionBody();
if (totalCacheCount != 0)
{
// Root object inline cache are not leaf
Js::ScriptContext* scriptContext = this->GetFunctionBody()->GetScriptContext();
void ** inlineCaches = RecyclerNewArrayZ(scriptContext->GetRecycler() ,
void*, totalCacheCount);
#if DBG
this->m_inlineCacheTypes = RecyclerNewArrayLeafZ(scriptContext->GetRecycler(),
byte, totalCacheCount);
#endif
uint i = 0;
uint plainInlineCacheEnd = rootObjectLoadInlineCacheStart;
__analysis_assume(plainInlineCacheEnd <= totalCacheCount);
for (; i < plainInlineCacheEnd; i++)
{
inlineCaches[i] = AllocatorNewZ(InlineCacheAllocator,
scriptContext->GetInlineCacheAllocator(), InlineCache);
}
Js::RootObjectBase * rootObject = functionBody->GetRootObject();
ThreadContext * threadContext = scriptContext->GetThreadContext();
uint rootObjectLoadInlineCacheEnd = rootObjectLoadMethodInlineCacheStart;
__analysis_assume(rootObjectLoadInlineCacheEnd <= totalCacheCount);
for (; i < rootObjectLoadInlineCacheEnd; i++)
{
inlineCaches[i] = rootObject->GetInlineCache(
threadContext->GetPropertyName(functionBody->GetPropertyIdFromCacheId(i)), false, false);
}
uint rootObjectLoadMethodInlineCacheEnd = rootObjectStoreInlineCacheStart;
__analysis_assume(rootObjectLoadMethodInlineCacheEnd <= totalCacheCount);
for (; i < rootObjectLoadMethodInlineCacheEnd; i++)
{
inlineCaches[i] = rootObject->GetInlineCache(
threadContext->GetPropertyName(functionBody->GetPropertyIdFromCacheId(i)), true, false);
}
uint rootObjectStoreInlineCacheEnd = isInstInlineCacheStart;
__analysis_assume(rootObjectStoreInlineCacheEnd <= totalCacheCount);
for (; i < rootObjectStoreInlineCacheEnd; i++)
{
#pragma prefast(suppress:6386, "The analysis assume didn't help prefast figure out this is in range")
inlineCaches[i] = rootObject->GetInlineCache(
threadContext->GetPropertyName(functionBody->GetPropertyIdFromCacheId(i)), false, true);
}
for (; i < totalCacheCount; i++)
{
inlineCaches[i] = AllocatorNewStructZ(CacheAllocator,
functionBody->GetScriptContext()->GetIsInstInlineCacheAllocator(), IsInstInlineCache);
}
#if DBG
this->m_inlineCacheTypes = RecyclerNewArrayLeafZ(functionBody->GetScriptContext()->GetRecycler(),
byte, totalCacheCount);
#endif
this->m_inlineCaches = inlineCaches;
}
}
bool ScriptFunction::GetSymbolName(const char16** symbolName, charcount_t* length) const
{
if (nullptr != this->computedNameVar && JavascriptSymbol::Is(this->computedNameVar))
{
const PropertyRecord* symbolRecord = JavascriptSymbol::FromVar(this->computedNameVar)->GetValue();
*symbolName = symbolRecord->GetBuffer();
*length = symbolRecord->GetLength();
return true;
}
*symbolName = nullptr;
*length = 0;
return false;
}
JavascriptString* ScriptFunction::GetDisplayNameImpl() const
{
Assert(this->GetFunctionProxy() != nullptr); // The caller should guarantee a proxy exists
ParseableFunctionInfo * func = this->GetFunctionProxy()->EnsureDeserialized();
const char16* name = nullptr;
charcount_t length = 0;
JavascriptString* returnStr = nullptr;
ENTER_PINNED_SCOPE(JavascriptString, computedName);
if (computedNameVar != nullptr)
{
const char16* symbolName = nullptr;
charcount_t symbolNameLength = 0;
if (this->GetSymbolName(&symbolName, &symbolNameLength))
{
if (symbolNameLength == 0)
{
name = symbolName;
}
else
{
name = FunctionProxy::WrapWithBrackets(symbolName, symbolNameLength, this->GetScriptContext());
length = symbolNameLength + 2; //adding 2 to length for brackets
}
}
else
{
computedName = this->GetComputedName();
if (!func->GetIsAccessor())
{
return computedName;
}
name = computedName->GetString();
length = computedName->GetLength();
}
}
else
{
name = Constants::Empty;
if (func->GetIsNamedFunctionExpression()) // GetIsNamedFunctionExpression -> ex. var a = function foo() {} where name is foo
{
name = func->GetShortDisplayName(&length);
}
else if (func->GetIsNameIdentifierRef()) // GetIsNameIdentifierRef -> confirms a name is not attached like o.x = function() {}
{
if (this->GetScriptContext()->GetConfig()->IsES6FunctionNameFullEnabled())
{
name = func->GetShortDisplayName(&length);
}
else if (func->GetIsDeclaration() || // GetIsDeclaration -> ex. function foo () {}
func->GetIsAccessor() || // GetIsAccessor -> ex. var a = { get f() {}} new enough syntax that we do not have to disable by default
func->IsLambda() || // IsLambda -> ex. var y = { o : () => {}}
GetHomeObj()) // GetHomeObj -> ex. var o = class {}, confirms this is a constructor or method on a class
{
name = func->GetShortDisplayName(&length);
}
}
}
AssertMsg(IsValidCharCount(length), "JavascriptString can't be larger than charcount_t");
returnStr = DisplayNameHelper(name, static_cast<charcount_t>(length));
LEAVE_PINNED_SCOPE();
return returnStr;
}
bool ScriptFunction::IsAnonymousFunction() const
{
return this->GetFunctionProxy()->GetIsAnonymousFunction();
}
JavascriptString* ScriptFunction::GetComputedName() const
{
JavascriptString* computedName = nullptr;
ScriptContext* scriptContext = this->GetScriptContext();
if (computedNameVar != nullptr)
{
if (TaggedInt::Is(computedNameVar))
{
computedName = TaggedInt::ToString(computedNameVar, scriptContext);
}
else
{
computedName = JavascriptConversion::ToString(computedNameVar, scriptContext);
}
return computedName;
}
return nullptr;
}
void ScriptFunctionWithInlineCache::ClearInlineCacheOnFunctionObject()
{
if (NULL != this->m_inlineCaches)
{
FreeOwnInlineCaches<false>();
this->m_inlineCaches = nullptr;
this->inlineCacheCount = 0;
this->rootObjectLoadInlineCacheStart = 0;
this->rootObjectLoadMethodInlineCacheStart = 0;
this->rootObjectStoreInlineCacheStart = 0;
this->isInstInlineCacheCount = 0;
}
SetHasInlineCaches(false);
}
void ScriptFunctionWithInlineCache::ClearBorrowedInlineCacheOnFunctionObject()
{
if (this->hasOwnInlineCaches)
{
return;
}
ClearInlineCacheOnFunctionObject();
}
}