blob: 18d49eccea67b12966fd2aa84b8d7cbe5d436070 [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"
using namespace Js;
ScriptFunctionBase::ScriptFunctionBase(DynamicType * type) :
JavascriptFunction(type)
{}
ScriptFunctionBase::ScriptFunctionBase(DynamicType * type, FunctionInfo * functionInfo) :
JavascriptFunction(type, functionInfo)
{}
template <> bool Js::VarIsImpl<ScriptFunctionBase>(RecyclableObject* obj)
{
if (VarIs<JavascriptFunction>(obj))
{
JavascriptFunction *function = UnsafeVarTo<JavascriptFunction>(obj);
return ScriptFunction::Test(function) || JavascriptGeneratorFunction::Test(function)
|| JavascriptAsyncFunction::Test(function) || JavascriptAsyncGeneratorFunction::Test(function);
}
return false;
}
ScriptFunction::ScriptFunction(FunctionProxy * proxy, ScriptFunctionType* type)
: ScriptFunctionBase(type, proxy->GetFunctionInfo()),
environment((FrameDisplay*)&NullFrameDisplay), cachedScopeObj(nullptr),
hasInlineCaches(false)
{
Assert(proxy->GetFunctionInfo()->GetFunctionProxy() == proxy);
Assert(proxy->GetDeferredPrototypeType() == type || proxy->GetUndeferredFunctionType() == type);
DebugOnly(VerifyEntryPoint());
#if ENABLE_NATIVE_CODEGEN
if (!proxy->IsDeferred())
{
FunctionBody* body = proxy->GetFunctionBody();
if(!body->GetNativeEntryPointUsed() &&
body->GetDefaultFunctionEntryPointInfo()->IsCodeGenDone())
{
MemoryBarrier();
#ifdef BGJIT_STATS
type->GetScriptContext()->jitCodeUsed += body->GetByteCodeCount();
type->GetScriptContext()->funcJitCodeUsed++;
#endif
body->SetNativeEntryPointUsed(true);
}
}
#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();
JIT_HELPER_NOT_REENTRANT_HEADER(ScrFunc_OP_NewScFunc, reentrancylock, scriptContext->GetThreadContext());
ScriptFunction * pfuncScript = nullptr;
if (functionProxy->IsFunctionBody() && functionProxy->GetFunctionBody()->GetInlineCachesOnFunctionObject())
{
FunctionBody * functionBody = functionProxy->GetFunctionBody();
if (functionBody->GetIsFirstFunctionObject())
{
functionBody->SetIsNotFirstFunctionObject();
}
else
{
ScriptFunctionWithInlineCache* pfuncScriptWithInlineCache = scriptContext->GetLibrary()->CreateScriptFunctionWithInlineCache(functionProxy);
// allocate inline cache for this function object
pfuncScriptWithInlineCache->CreateInlineCache();
Assert(functionBody->GetInlineCacheCount() + functionBody->GetIsInstInlineCacheCount());
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();
}
pfuncScript = pfuncScriptWithInlineCache;
}
}
if (pfuncScript == nullptr)
{
pfuncScript = scriptContext->GetLibrary()->CreateScriptFunction(functionProxy);
}
pfuncScript->SetEnvironment(environment);
ScriptFunctionType *scFuncType = functionProxy->GetUndeferredFunctionType();
if (scFuncType && pfuncScript->GetType() == functionProxy->GetDeferredPrototypeType())
{
pfuncScript->GetTypeHandler()->EnsureObjectReady(pfuncScript);
}
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(pfuncScript, EtwTrace::GetFunctionId(functionProxy)));
return pfuncScript;
JIT_HELPER_END(ScrFunc_OP_NewScFunc);
}
ScriptFunction * ScriptFunction::OP_NewScFuncHomeObj(FrameDisplay *environment, FunctionInfoPtrPtr infoRef, Var homeObj)
{
Assert(homeObj != nullptr);
Assert((*infoRef)->GetFunctionProxy()->GetFunctionInfo()->HasHomeObj());
JIT_HELPER_NOT_REENTRANT_HEADER(ScrFunc_OP_NewScFuncHomeObj, reentrancylock, (*infoRef)->GetFunctionProxy()->GetScriptContext()->GetThreadContext());
ScriptFunction* scriptFunc = ScriptFunction::OP_NewScFunc(environment, infoRef);
scriptFunc->SetHomeObj(homeObj);
// After setting homeobject we need to set the name if the object is ready.
if ((*infoRef)->GetFunctionProxy()->GetUndeferredFunctionType())
{
if (!scriptFunc->IsAnonymousFunction() &&
!scriptFunc->GetFunctionInfo()->IsClassConstructor() &&
!scriptFunc->GetFunctionProxy()->EnsureDeserialized()->GetIsStaticNameFunction())
{
JavascriptString * functionName = scriptFunc->GetDisplayNameImpl();
scriptFunc->SetPropertyWithAttributes(PropertyIds::name, functionName, PropertyConfigurable, nullptr);
}
}
return scriptFunc;
JIT_HELPER_END(ScrFunc_OP_NewScFuncHomeObj);
}
ScriptFunction * ScriptFunction::OP_NewClassConstructor(FrameDisplay *environment, FunctionInfoPtrPtr infoRef, Var homeObj, RecyclableObject *constructorParent)
{
JIT_HELPER_NOT_REENTRANT_HEADER(ScrFunc_OP_NewClassConstructor, reentrancylock, (*infoRef)->GetFunctionProxy()->GetScriptContext()->GetThreadContext());
FunctionProxy * proxy = (*infoRef)->GetFunctionProxy();
if (proxy->GetUndeferredFunctionType() == nullptr)
{
DynamicTypeHandler * typeHandler = proxy->GetIsAnonymousFunction()
? proxy->GetScriptContext()->GetLibrary()->AnonymousClassConstructorTypeHandler()
: proxy->GetScriptContext()->GetLibrary()->ClassConstructorTypeHandler();
ScriptFunctionType * newType = ScriptFunctionType::New(proxy, typeHandler, constructorParent, true);
proxy->SetUndeferredFunctionType(newType);
}
ScriptFunction * scriptFunction = OP_NewScFuncHomeObj(environment, infoRef, homeObj);
if (scriptFunction->GetPrototype() != constructorParent)
{
scriptFunction->SetPrototype(constructorParent);
}
Var length = TaggedInt::ToVarUnchecked((*infoRef)->GetFunctionProxy()->EnsureDeserialized()->GetReportedInParamsCount() - 1);
scriptFunction->SetSlot(SetSlotArguments(Constants::NoProperty, 1, length));
if (!scriptFunction->IsAnonymousFunction() && !scriptFunction->GetFunctionInfo()->HasComputedName())
{
JavascriptString * functionName = nullptr;
bool result = scriptFunction->GetFunctionName(&functionName);
Assert(result);
scriptFunction->SetSlot(SetSlotArguments(Constants::NoProperty, 2, functionName));
}
return scriptFunction;
JIT_HELPER_END(ScrFunc_OP_NewClassConstructor);
}
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 = VarTo<RecyclableObject>(scope);
scopeObj->InvalidateCachedScope();
}
}
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;
}
void ScriptFunction::PrepareForConversionToNonPathType()
{
// We have a path type handler that is currently responsible for holding some number of entry point infos alive.
// The last one will be copied on to the new dictionary type handler, but if any previous instances in the path
// are holding different entry point infos, those need to be copied to somewhere safe.
// The number of entry points is likely low compared to length of path, so iterate those instead.
ProxyEntryPointInfo* entryPointInfo = this->GetScriptFunctionType()->GetEntryPointInfo();
this->GetFunctionProxy()->MapFunctionObjectTypes([&](ScriptFunctionType* functionType)
{
CopyEntryPointInfoToThreadContextIfNecessary(functionType->GetEntryPointInfo(), entryPointInfo);
});
}
void ScriptFunction::ReplaceTypeWithPredecessorType(DynamicType * previousType)
{
ProxyEntryPointInfo* oldEntryPointInfo = this->GetScriptFunctionType()->GetEntryPointInfo();
__super::ReplaceTypeWithPredecessorType(previousType);
ProxyEntryPointInfo* newEntryPointInfo = this->GetScriptFunctionType()->GetEntryPointInfo();
CopyEntryPointInfoToThreadContextIfNecessary(oldEntryPointInfo, newEntryPointInfo);
}
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(this->GetTypeId() == TypeIds_Function);
#if ENABLE_NATIVE_CODEGEN
Assert(!IsCrossSiteObject() || entryPoint != (Js::JavascriptMethod)checkCodeGenThunk);
#endif
Assert((entryPointInfo != nullptr && this->GetFunctionProxy() != nullptr));
bool isAsmJS = HasFunctionBody() && this->GetFunctionBody()->GetIsAsmjsMode();
this->GetScriptFunctionType()->ChangeEntryPoint(entryPointInfo, entryPoint, isAsmJS);
}
void ScriptFunction::CopyEntryPointInfoToThreadContextIfNecessary(ProxyEntryPointInfo* oldEntryPointInfo, ProxyEntryPointInfo* newEntryPointInfo)
{
if (oldEntryPointInfo
&& oldEntryPointInfo != newEntryPointInfo
&& 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);
}
}
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 == newFunctionInfo->GetFunctionInfo());
Assert(this->functionInfo->GetFunctionBody() == newFunctionInfo);
Assert(!newFunctionInfo->IsDeferred());
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();
}
JavascriptString * ScriptFunction::EnsureSourceString()
{
// The function may be defer serialize, need to be deserialized
FunctionProxy* proxy = this->GetFunctionProxy();
ParseableFunctionInfo * pFuncBody = proxy->EnsureDeserialized();
JavascriptString * 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->GetToStringSource(_u("ScriptFunction::EnsureSourceString"));
size_t cbPreludeLength = 0;
// cch and cbLength refer to the length of the parse, which may be smaller than the length of the to-string function
PrintOffsets* printOffsets = pFuncBody->GetPrintOffsets();
if (printOffsets != nullptr)
{
Assert((printOffsets->cbEndPrintOffset - printOffsets->cbStartPrintOffset) >= cbLength);
cbPreludeLength = (printOffsets->cbEndPrintOffset - printOffsets->cbStartPrintOffset) - cbLength;
Assert(pFuncBody->StartOffset() >= printOffsets->cbStartPrintOffset);
cbLength = printOffsets->cbEndPrintOffset - printOffsets->cbStartPrintOffset;
}
Assert(cbPreludeLength < MaxCharCount);
// the toString of a function may include some prelude, e.g. the computed name expression.
// We do not store the char-index of the start, but if there are cbPreludeLength bytes difference,
// then that is an upper bound on the number of characters difference.
// We also assume that function.toString is relatively infrequent, and non-ascii characters in
// a prelude are relatively infrequent, so the inaccuracy here should in general be insignificant
BufferStringBuilder builder(cch + static_cast<charcount_t>(cbPreludeLength), 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();
}
else if (decodedCount < cch + static_cast<charcount_t>(cbPreludeLength))
{
Recycler* recycler = scriptContext->GetRecycler();
char16* buffer = RecyclerNewArrayLeaf(recycler, char16, decodedCount + 1);
wmemcpy_s(buffer, decodedCount, builder.DangerousGetWritableBuffer(), decodedCount);
buffer[decodedCount] = 0;
cachedSourceString = JavascriptString::NewWithBuffer(buffer, static_cast<charcount_t>(decodedCount), scriptContext);
}
else
{
cachedSourceString = 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->GetComputedNameVar() != nullptr)
{
extractor->MarkVisitVar(this->GetComputedNameVar());
}
if (this->GetHomeObj() != nullptr)
{
extractor->MarkVisitVar(this->GetHomeObj());
}
}
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 = (Field(Js::Var)*)scope;
uint slotArrayCount = static_cast<uint>(slotArray.GetCount());
//get the function body associated with the scope
if (slotArray.IsDebuggerScopeSlotArray())
{
rctxInfo->AddWellKnownDebuggerScopePath(this, slotArray.GetDebuggerScope(), i);
}
else
{
rctxInfo->EnqueueNewFunctionBodyObject(this, slotArray.GetFunctionInfo()->GetFunctionBody(), scopePathString.GetStrValue());
}
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->GetComputedNameVar() != nullptr)
{
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->GetComputedNameVar(), _u("_computedName"));
}
if (this->GetHomeObj() != nullptr)
{
this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->GetHomeObj(), _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>();
this->ExtractSnapObjectDataIntoSnapScriptFunctionInfo(ssfi, alloc);
TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapScriptFunctionInfo*, TTD::NSSnapObjects::SnapObjectType::SnapScriptFunctionObject>(objData, ssfi);
}
// TODO: Fixup function definition - something funky w/ header file includes - cycles?
void ScriptFunction::ExtractSnapObjectDataIntoSnapScriptFunctionInfo(/*TTD::NSSnapObjects::SnapScriptFunctionInfo* */ void* snapScriptFunctionInfo, TTD::SlabAllocator& alloc)
{
TTD::NSSnapObjects::SnapScriptFunctionInfo* ssfi = reinterpret_cast<TTD::NSSnapObjects::SnapScriptFunctionInfo*>(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->GetHomeObj() != nullptr)
{
ssfi->HomeObjId = TTD_CONVERT_VAR_TO_PTR_ID(this->GetHomeObj());
}
ssfi->ComputedNameInfo = TTD_CONVERT_JSVAR_TO_TTDVAR(this->GetComputedNameVar());
}
#endif
AsmJsScriptFunction::AsmJsScriptFunction(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType) :
ScriptFunction(proxy, deferredPrototypeType), m_moduleEnvironment(nullptr)
{}
AsmJsScriptFunction * AsmJsScriptFunction::OP_NewAsmJsFunc(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();
Assert(!functionProxy->HasSuperReference());
AsmJsScriptFunction* asmJsFunc = scriptContext->GetLibrary()->CreateAsmJsScriptFunction(functionProxy);
asmJsFunc->SetEnvironment(environment);
JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_FUNCTION(asmJsFunc, EtwTrace::GetFunctionId(functionProxy)));
return asmJsFunc;
}
JavascriptArrayBuffer* AsmJsScriptFunction::GetAsmJsArrayBuffer() const
{
#ifdef ASMJS_PLAT
return (JavascriptArrayBuffer*)PointerValue(
*(this->GetModuleEnvironment() + AsmJsModuleMemory::MemoryTableBeginOffset));
#else
Assert(UNREACHED);
return nullptr;
#endif
}
#ifdef ENABLE_WASM
WasmScriptFunction::WasmScriptFunction(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType) :
AsmJsScriptFunction(proxy, deferredPrototypeType), m_signature(nullptr)
{
Assert(!proxy->GetFunctionInfo()->HasComputedName());
}
WebAssemblyMemory* WasmScriptFunction::GetWebAssemblyMemory() const
{
return (WebAssemblyMemory*)PointerValue(
*(this->GetModuleEnvironment() + AsmJsModuleMemory::MemoryTableBeginOffset));
}
#endif
ScriptFunctionWithInlineCache::ScriptFunctionWithInlineCache(FunctionProxy * proxy, ScriptFunctionType* deferredPrototypeType) :
ScriptFunction(proxy, deferredPrototypeType)
{}
InlineCache * ScriptFunctionWithInlineCache::GetInlineCache(uint index)
{
void** inlineCaches = this->GetInlineCaches();
AssertOrFailFast(inlineCaches != nullptr);
AssertOrFailFast(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(inlineCaches[index]));
}
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();
}
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)
{
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(Var computedNameVar, const char16** symbolName, charcount_t* length)
{
if (nullptr != computedNameVar && VarIs<JavascriptSymbol>(computedNameVar))
{
const PropertyRecord* symbolRecord = VarTo<JavascriptSymbol>(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);
Var computedNameVar = this->GetComputedNameVar();
if (computedNameVar != nullptr)
{
const char16* symbolName = nullptr;
charcount_t symbolNameLength = 0;
if (ScriptFunction::GetSymbolName(computedNameVar, &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 = ScriptFunction::GetComputedName(computedNameVar, this->GetScriptContext());
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(Var computedNameVar, ScriptContext * scriptContext)
{
JavascriptString* computedName = nullptr;
if (computedNameVar != nullptr)
{
if (TaggedInt::Is(computedNameVar))
{
computedName = TaggedInt::ToString(computedNameVar, scriptContext);
}
else
{
computedName = JavascriptConversion::ToString(computedNameVar, scriptContext);
}
return computedName;
}
return nullptr;
}
bool ScriptFunction::HasSuperReference()
{
return this->GetFunctionProxy()->HasSuperReference();
}
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);
}
template <>
void FunctionWithComputedName<ScriptFunction>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
}
template <>
void FunctionWithComputedName<AsmJsScriptFunction>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
}
template <>
void FunctionWithComputedName<ScriptFunctionWithInlineCache>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
}
template <>
void FunctionWithComputedName<GeneratorVirtualScriptFunction>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
}
template <>
void FunctionWithComputedName<FunctionWithHomeObj<GeneratorVirtualScriptFunction>>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
}
template <>
void FunctionWithComputedName<FunctionWithHomeObj<ScriptFunction>>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
if (GetFunctionInfo()->IsClassConstructor())
{
// For class with computed name, we wait until now to set the name property.
JavascriptString * functionName = nullptr;
bool result = GetFunctionName(&functionName);
Assert(result);
SetSlot(SetSlotArguments(Constants::NoProperty, 2, functionName));
}
}
template <>
void FunctionWithComputedName<FunctionWithHomeObj<ScriptFunctionWithInlineCache>>::SetComputedNameVar(Var computedNameVar)
{
this->computedNameVar = computedNameVar;
if (GetFunctionInfo()->IsClassConstructor())
{
// For class with computed name, we wait until now to set the name property.
JavascriptString * functionName = nullptr;
bool result = GetFunctionName(&functionName);
Assert(result);
SetSlot(SetSlotArguments(Constants::NoProperty, 2, functionName));
}
}
template <> VTableValue Js::FunctionWithComputedName<Js::AsmJsScriptFunction>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableAsmJsScriptFunctionWithComputedName;
}
template <> VTableValue Js::FunctionWithHomeObj<Js::ScriptFunction>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableScriptFunctionWithHomeObj;
}
template <> VTableValue Js::FunctionWithComputedName<Js::ScriptFunction>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableScriptFunctionWithComputedName;
}
template <> VTableValue Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::ScriptFunction>>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableScriptFunctionWithHomeObjAndComputedName;
}
template <> VTableValue Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableScriptFunctionWithInlineCacheAndHomeObj;
}
template <> VTableValue Js::FunctionWithComputedName<Js::ScriptFunctionWithInlineCache>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableScriptFunctionWithInlineCacheAndComputedName;
}
template <> VTableValue Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableScriptFunctionWithInlineCacheHomeObjAndComputedName;
}
template <> VTableValue Js::FunctionWithHomeObj<Js::GeneratorVirtualScriptFunction>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableVirtualJavascriptGeneratorFunctionWithHomeObj;
}
template <> VTableValue Js::FunctionWithComputedName<Js::GeneratorVirtualScriptFunction>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableVirtualJavascriptGeneratorFunctionWithComputedName;
}
template <> VTableValue Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::GeneratorVirtualScriptFunction>>::DummyVirtualFunctionToHinderLinkerICF() const
{
return VTableValue::VtableVirtualJavascriptGeneratorFunctionWithHomeObjAndComputedName;
}