blob: 42e0380716a41fbbaae521e82548e1871d93d477 [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 "RuntimeLanguagePch.h"
#if ENABLE_PROFILE_INFO
#ifdef ENABLE_WININET_PROFILE_DATA_CACHE
#include "activscp_private.h"
#endif
namespace Js
{
ExecutionFlags
SourceDynamicProfileManager::IsFunctionExecuted(Js::LocalFunctionId functionId)
{
if (cachedStartupFunctions == nullptr || cachedStartupFunctions->Length() <= functionId)
{
return ExecutionFlags_HasNoInfo;
}
return (ExecutionFlags)cachedStartupFunctions->Test(functionId);
}
DynamicProfileInfo *
SourceDynamicProfileManager::GetDynamicProfileInfo(FunctionBody * functionBody)
{
Js::LocalFunctionId functionId = functionBody->GetLocalFunctionId();
DynamicProfileInfo * dynamicProfileInfo = nullptr;
if (dynamicProfileInfoMap.Count() > 0 && dynamicProfileInfoMap.TryGetValue(functionId, &dynamicProfileInfo))
{
if (dynamicProfileInfo->MatchFunctionBody(functionBody))
{
return dynamicProfileInfo;
}
#if DBG_DUMP
if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::DynamicProfilePhase))
{
Output::Print(_u("TRACE: DynamicProfile: Profile data rejected for function %d in %s\n"),
functionId, functionBody->GetSourceContextInfo()->url);
Output::Flush();
}
#endif
// NOTE: We have profile mismatch, we can invalidate all other profile here.
}
return nullptr;
}
void SourceDynamicProfileManager::UpdateDynamicProfileInfo(LocalFunctionId functionId, DynamicProfileInfo * dynamicProfileInfo)
{
Assert(dynamicProfileInfo != nullptr);
dynamicProfileInfoMap.Item(functionId, dynamicProfileInfo);
}
void SourceDynamicProfileManager::MarkAsExecuted(LocalFunctionId functionId)
{
Assert(startupFunctions != nullptr);
Assert(functionId <= startupFunctions->Length());
startupFunctions->Set(functionId);
}
void SourceDynamicProfileManager::EnsureStartupFunctions(uint numberOfFunctions)
{
Assert(numberOfFunctions != 0);
if(!startupFunctions || numberOfFunctions > startupFunctions->Length())
{
BVFixed* oldStartupFunctions = this->startupFunctions;
startupFunctions = BVFixed::New(numberOfFunctions, this->GetRecycler());
if(oldStartupFunctions)
{
this->startupFunctions->Copy(oldStartupFunctions);
}
}
}
//
// Enables re-use of profile managers across script contexts - on every re-use the
// previous script contexts list of startup functions are transferred over as input to this new script context.
//
void SourceDynamicProfileManager::Reuse()
{
AssertMsg(profileDataCache == nullptr, "Persisted profiles cannot be re-used");
cachedStartupFunctions = startupFunctions;
}
//
// Loads the profile from the WININET cache
//
bool SourceDynamicProfileManager::LoadFromProfileCache(IActiveScriptDataCache* profileDataCache, LPCWSTR url)
{
#ifdef ENABLE_WININET_PROFILE_DATA_CACHE
AssertMsg(CONFIG_FLAG(WininetProfileCache), "Profile caching should be enabled for us to get here");
Assert(profileDataCache);
AssertMsg(!IsProfileLoadedFromWinInet(), "Duplicate profile cache loading?");
// Keep a copy of this and addref it
profileDataCache->AddRef();
this->profileDataCache = profileDataCache;
IStream* readStream;
HRESULT hr = profileDataCache->GetReadDataStream(&readStream);
if(SUCCEEDED(hr))
{
Assert(readStream != nullptr);
// stream reader owns the stream and will close it on destruction
SimpleStreamReader streamReader(readStream);
DWORD jscriptMajorVersion;
DWORD jscriptMinorVersion;
if(FAILED(AutoSystemInfo::GetJscriptFileVersion(&jscriptMajorVersion, &jscriptMinorVersion)))
{
return false;
}
DWORD majorVersion;
if(!streamReader.Read(&majorVersion) || majorVersion != jscriptMajorVersion)
{
return false;
}
DWORD minorVersion;
if(!streamReader.Read(&minorVersion) || minorVersion != jscriptMinorVersion)
{
return false;
}
uint numberOfFunctions;
if(!streamReader.Read(&numberOfFunctions) || numberOfFunctions > MAX_FUNCTION_COUNT)
{
return false;
}
BVFixed* functions = BVFixed::New(numberOfFunctions, this->recycler);
if(!streamReader.ReadArray(functions->GetData(), functions->WordCount()))
{
return false;
}
this->cachedStartupFunctions = functions;
OUTPUT_TRACE(Js::DynamicProfilePhase, _u("Profile load succeeded. Function count: %d %s\n"), numberOfFunctions, url);
#if DBG_DUMP
if(PHASE_TRACE1(Js::DynamicProfilePhase) && Js::Configuration::Global.flags.Verbose)
{
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Profile loaded:\n"));
functions->Dump();
}
#endif
return true;
}
else if (hr == HRESULT_FROM_WIN32(ERROR_WRITE_PROTECT))
{
this->isNonCachableScript = true;
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Profile load failed. Non-cacheable resource. %s\n"), url);
}
else
{
OUTPUT_TRACE(Js::DynamicProfilePhase, _u("Profile load failed. No read stream. %s\n"), url);
}
#endif
return false;
}
//
// Saves the profile to the WININET cache and returns the bytes written
//
uint SourceDynamicProfileManager::SaveToProfileCacheAndRelease(SourceContextInfo* info)
{
uint bytesWritten = 0;
#ifdef ENABLE_WININET_PROFILE_DATA_CACHE
if(profileDataCache)
{
if(ShouldSaveToProfileCache(info))
{
OUTPUT_TRACE(Js::DynamicProfilePhase, _u("Saving profile. Number of functions: %d Url: %s...\n"), startupFunctions->Length(), info->url);
bytesWritten = SaveToProfileCache();
if(bytesWritten == 0)
{
OUTPUT_TRACE(Js::DynamicProfilePhase, _u("Profile saving FAILED\n"));
}
}
profileDataCache->Release();
profileDataCache = nullptr;
}
#endif
return bytesWritten;
}
//
// Saves the profile to the WININET cache
//
uint SourceDynamicProfileManager::SaveToProfileCache()
{
AssertMsg(CONFIG_FLAG(WininetProfileCache), "Profile caching should be enabled for us to get here");
Assert(startupFunctions);
uint bytesWritten = 0;
#ifdef ENABLE_WININET_PROFILE_DATA_CACHE
//TODO: Add some diffing logic to not write unless necessary
IStream* writeStream;
HRESULT hr = profileDataCache->GetWriteDataStream(&writeStream);
if(FAILED(hr))
{
return 0;
}
Assert(writeStream != nullptr);
// stream writer owns the stream and will close it on destruction
SimpleStreamWriter streamWriter(writeStream);
DWORD jscriptMajorVersion;
DWORD jscriptMinorVersion;
if(FAILED(AutoSystemInfo::GetJscriptFileVersion(&jscriptMajorVersion, &jscriptMinorVersion)))
{
return 0;
}
if(!streamWriter.Write(jscriptMajorVersion))
{
return 0;
}
if(!streamWriter.Write(jscriptMinorVersion))
{
return 0;
}
if(!streamWriter.Write(startupFunctions->Length()))
{
return 0;
}
if(streamWriter.WriteArray(startupFunctions->GetData(), startupFunctions->WordCount()))
{
STATSTG stats;
if(SUCCEEDED(writeStream->Stat(&stats, STATFLAG_NONAME)))
{
bytesWritten = stats.cbSize.LowPart;
Assert(stats.cbSize.LowPart > 0);
AssertMsg(stats.cbSize.HighPart == 0, "We should not be writing such long data that the high part is non-zero");
}
hr = profileDataCache->SaveWriteDataStream(writeStream);
if(FAILED(hr))
{
return 0;
}
#if DBG_DUMP
if(PHASE_TRACE1(Js::DynamicProfilePhase) && Js::Configuration::Global.flags.Verbose)
{
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Saved profile:\n"));
startupFunctions->Dump();
}
#endif
}
#endif
return bytesWritten;
}
//
// Do not save the profile:
// - If it is a non-cacheable WININET resource
// - If there are no or small number of functions executed
// - If there is not substantial difference in number of functions executed.
//
bool SourceDynamicProfileManager::ShouldSaveToProfileCache(SourceContextInfo* info) const
{
if(isNonCachableScript)
{
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Skipping save of profile. Non-cacheable resource. %s\n"), info->url);
return false;
}
if(!startupFunctions || startupFunctions->Length() <= DEFAULT_CONFIG_MinProfileCacheSize)
{
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Skipping save of profile. Small number of functions. %s\n"), info->url);
return false;
}
if(cachedStartupFunctions)
{
AssertMsg(cachedStartupFunctions != startupFunctions, "Ensure they are not shallow copies of each other - Reuse() does this for dynamic sources. We should not be invoked for dynamic sources");
uint numberOfBitsDifferent = cachedStartupFunctions->DiffCount(startupFunctions);
uint saveThreshold = (cachedStartupFunctions->Length() * DEFAULT_CONFIG_ProfileDifferencePercent) / 100;
if(numberOfBitsDifferent <= saveThreshold)
{
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Skipping save of profile. Number of functions different: %d %s\n"), numberOfBitsDifferent, info->url);
return false;
}
else
{
OUTPUT_VERBOSE_TRACE(Js::DynamicProfilePhase, _u("Number of functions different: %d "), numberOfBitsDifferent);
}
}
return true;
}
SourceDynamicProfileManager *
SourceDynamicProfileManager::LoadFromDynamicProfileStorage(SourceContextInfo* info, ScriptContext* scriptContext, IActiveScriptDataCache* profileDataCache)
{
SourceDynamicProfileManager* manager = nullptr;
Recycler* recycler = scriptContext->GetRecycler();
#ifdef DYNAMIC_PROFILE_STORAGE
if(DynamicProfileStorage::IsEnabled() && info->url != nullptr)
{
manager = DynamicProfileStorage::Load(info->url, [recycler](char const * buffer, uint length) -> SourceDynamicProfileManager *
{
BufferReader reader(buffer, length);
return SourceDynamicProfileManager::Deserialize(&reader, recycler);
});
}
#endif
if(manager == nullptr)
{
manager = RecyclerNew(recycler, SourceDynamicProfileManager, recycler);
}
if(profileDataCache != nullptr)
{
bool profileLoaded = manager->LoadFromProfileCache(profileDataCache, info->url);
if(profileLoaded)
{
JS_ETW(EventWriteJSCRIPT_PROFILE_LOAD(info->dwHostSourceContext, scriptContext));
}
}
return manager;
}
#ifdef DYNAMIC_PROFILE_STORAGE
void SourceDynamicProfileManager::ClearSavingData()
{
dynamicProfileInfoMapSaving.Reset();
}
void SourceDynamicProfileManager::CopySavingData()
{
dynamicProfileInfoMap.Map([&](LocalFunctionId functionId, DynamicProfileInfo *info)
{
dynamicProfileInfoMapSaving.Item(functionId, info);
});
}
void
SourceDynamicProfileManager::SaveDynamicProfileInfo(LocalFunctionId functionId, DynamicProfileInfo * dynamicProfileInfo)
{
Assert(dynamicProfileInfo->GetFunctionBody()->HasExecutionDynamicProfileInfo());
dynamicProfileInfoMapSaving.Item(functionId, dynamicProfileInfo);
}
template <typename T>
SourceDynamicProfileManager *
SourceDynamicProfileManager::Deserialize(T * reader, Recycler* recycler)
{
uint functionCount;
if (!reader->Peek(&functionCount))
{
return nullptr;
}
BVFixed * startupFunctions = BVFixed::New(functionCount, recycler);
if (!reader->ReadArray(((char *)startupFunctions),
BVFixed::GetAllocSize(functionCount)))
{
return nullptr;
}
uint profileCount;
if (!reader->Read(&profileCount))
{
return nullptr;
}
ThreadContext* threadContext = ThreadContext::GetContextForCurrentThread();
SourceDynamicProfileManager * sourceDynamicProfileManager = RecyclerNew(threadContext->GetRecycler(), SourceDynamicProfileManager, recycler);
sourceDynamicProfileManager->cachedStartupFunctions = startupFunctions;
#if DBG_DUMP
if(Configuration::Global.flags.Dump.IsEnabled(DynamicProfilePhase))
{
Output::Print(_u("Loaded: Startup functions bit vector:"));
startupFunctions->Dump();
}
#endif
for (uint i = 0; i < profileCount; i++)
{
Js::LocalFunctionId functionId;
DynamicProfileInfo * dynamicProfileInfo = DynamicProfileInfo::Deserialize(reader, recycler, &functionId);
if (dynamicProfileInfo == nullptr || functionId >= functionCount)
{
return nullptr;
}
sourceDynamicProfileManager->dynamicProfileInfoMap.Add(functionId, dynamicProfileInfo);
}
return sourceDynamicProfileManager;
}
template <typename T>
bool
SourceDynamicProfileManager::Serialize(T * writer)
{
// To simulate behavior of in memory profile cache - let's keep functions marked as executed if they were loaded
// to be so from the profile - this helps with ensure inlined functions are marked as executed.
if(!this->startupFunctions)
{
this->startupFunctions = const_cast<BVFixed*>(static_cast<const BVFixed*>(this->cachedStartupFunctions));
}
else if(cachedStartupFunctions && this->cachedStartupFunctions->Length() == this->startupFunctions->Length())
{
this->startupFunctions->Or(cachedStartupFunctions);
}
if(this->startupFunctions)
{
#if DBG_DUMP
if(Configuration::Global.flags.Dump.IsEnabled(DynamicProfilePhase))
{
Output::Print(_u("Saving: Startup functions bit vector:"));
this->startupFunctions->Dump();
}
#endif
size_t bvSize = BVFixed::GetAllocSize(this->startupFunctions->Length()) ;
if (!writer->WriteArray((char *)static_cast<BVFixed*>(this->startupFunctions), bvSize)
|| !writer->Write(this->dynamicProfileInfoMapSaving.Count()))
{
return false;
}
}
for (int i = 0; i < this->dynamicProfileInfoMapSaving.Count(); i++)
{
DynamicProfileInfo * dynamicProfileInfo = this->dynamicProfileInfoMapSaving.GetValueAt(i);
if (dynamicProfileInfo == nullptr || !dynamicProfileInfo->HasFunctionBody())
{
continue;
}
if (!dynamicProfileInfo->Serialize(writer))
{
return false;
}
}
return true;
}
void
SourceDynamicProfileManager::SaveToDynamicProfileStorage(char16 const * url)
{
Assert(DynamicProfileStorage::IsEnabled());
BufferSizeCounter counter;
if (!this->Serialize(&counter))
{
return;
}
if (counter.GetByteCount() > UINT_MAX)
{
// too big
return;
}
char * record = DynamicProfileStorage::AllocRecord(static_cast<DWORD>(counter.GetByteCount()));
#if DBG_DUMP
if (PHASE_STATS1(DynamicProfilePhase))
{
Output::Print(_u("%-180s : %d bytes\n"), url, counter.GetByteCount());
}
#endif
BufferWriter writer(DynamicProfileStorage::GetRecordBuffer(record), counter.GetByteCount());
if (!this->Serialize(&writer))
{
Assert(false);
DynamicProfileStorage::DeleteRecord(record);
}
DynamicProfileStorage::SaveRecord(url, record);
}
#endif
};
#endif