Implement experimental code cache of function

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
Seonghyun Kim 2023-11-21 12:48:40 +09:00 committed by Hyukwoo Park
commit fcc35d22bf
7 changed files with 182 additions and 46 deletions

View file

@ -154,6 +154,10 @@ ENDIF()
IF (ESCARGOT_TEST)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DESCARGOT_ENABLE_TEST)
IF (ESCARGOT_CODE_CACHE)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DCODE_CACHE_MIN_SOURCE_LENGTH=1024)
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DCODE_CACHE_MAX_CACHE_NUM=128)
ENDIF()
ENDIF()
#######################################################

View file

@ -3054,7 +3054,7 @@ void ContextRef::setSecurityPolicyCheckCallback(SecurityPolicyCheckCallback cb)
StackOverflowDisabler::StackOverflowDisabler(ExecutionStateRef* es)
: m_executionState(es)
, m_originStackLimit(toImpl(es)->stackLimit())
, m_originStackLimit(ThreadLocal::stackLimit())
{
#ifdef STACK_GROWS_DOWN
size_t newStackLimit = 0;

View file

@ -299,19 +299,20 @@ void CodeCache::reset()
void CodeCache::setCacheEntry(const CodeCacheEntryChunk& entryChunk)
{
CodeCacheItem item(entryChunk.m_srcHash, entryChunk.m_functionSourceIndex);
#ifndef NDEBUG
auto iter = m_cacheList.find(entryChunk.m_srcHash);
auto iter = m_cacheList.find(item);
ASSERT(iter == m_cacheList.end());
#endif
m_cacheList.insert(std::make_pair(entryChunk.m_srcHash, entryChunk.m_entry));
m_cacheList.insert(std::make_pair(item, entryChunk.m_entry));
}
bool CodeCache::addCacheEntry(size_t hash, const CodeCacheEntry& entry)
bool CodeCache::addCacheEntry(size_t hash, Optional<size_t> functionSourceIndex, const CodeCacheEntry& entry)
{
ASSERT(m_enabled);
#ifndef NDEBUG
auto iter = m_cacheList.find(hash);
auto iter = m_cacheList.find(CodeCacheItem(hash, functionSourceIndex));
ASSERT(iter == m_cacheList.end());
#endif
if (m_cacheList.size() == CODE_CACHE_MAX_CACHE_NUM) {
@ -320,7 +321,7 @@ bool CodeCache::addCacheEntry(size_t hash, const CodeCacheEntry& entry)
}
}
m_cacheList.insert(std::make_pair(hash, entry));
m_cacheList.insert(std::make_pair(CodeCacheItem(hash, functionSourceIndex), entry));
return true;
}
@ -332,7 +333,7 @@ bool CodeCache::removeLRUCacheEntry()
#ifndef NDEBUG
uint64_t currentTimeStamp = fastTickCount();
#endif
size_t lruHash = 0;
CodeCacheItem lruItem(0, Optional<size_t>());
uint64_t lruTimeStamp = std::numeric_limits<uint64_t>::max();
for (auto iter = m_cacheList.begin(); iter != m_cacheList.end(); iter++) {
uint64_t timeStamp = iter->second.m_lastWrittenTimeStamp;
@ -340,27 +341,30 @@ bool CodeCache::removeLRUCacheEntry()
ASSERT(timeStamp <= currentTimeStamp);
#endif
if (lruTimeStamp > timeStamp) {
lruHash = iter->first;
lruItem = iter->first;
lruTimeStamp = timeStamp;
}
}
if (UNLIKELY(!removeCacheFile(lruHash))) {
if (UNLIKELY(!removeCacheFile(lruItem.m_srcHash, lruItem.m_functionSourceIndex))) {
return false;
}
size_t eraseReturn = m_cacheList.erase(lruHash);
size_t eraseReturn = m_cacheList.erase(lruItem);
ASSERT(eraseReturn == 1 && m_cacheList.size() == CODE_CACHE_MAX_CACHE_NUM - 1);
return true;
}
bool CodeCache::removeCacheFile(size_t hash)
bool CodeCache::removeCacheFile(size_t hash, Optional<size_t> functionSourceIndex)
{
ASSERT(m_enabled);
ASSERT(m_cacheDirPath.length());
std::string filePath = m_cacheDirPath + std::to_string(hash);
if (functionSourceIndex) {
filePath += "." + std::to_string(functionSourceIndex.value());
}
if (remove(filePath.data()) != 0) {
ESCARGOT_LOG_ERROR("[CodeCache] can`t remove a cache file %s\n", filePath.data());
@ -370,14 +374,14 @@ bool CodeCache::removeCacheFile(size_t hash)
return true;
}
std::pair<bool, CodeCacheEntry> CodeCache::searchCache(size_t srcHash)
std::pair<bool, CodeCacheEntry> CodeCache::searchCache(size_t srcHash, Optional<size_t> functionSourceIndex)
{
ASSERT(m_enabled);
CodeCacheEntry entry;
bool cacheHit = false;
auto iter = m_cacheList.find(srcHash);
auto iter = m_cacheList.find(CodeCacheItem(srcHash, functionSourceIndex));
if (iter != m_cacheList.end()) {
cacheHit = true;
entry = iter->second;
@ -386,7 +390,7 @@ std::pair<bool, CodeCacheEntry> CodeCache::searchCache(size_t srcHash)
return std::make_pair(cacheHit, entry);
}
void CodeCache::prepareCacheLoading(Context* context, size_t srcHash, const CodeCacheEntry& entry)
void CodeCache::prepareCacheLoading(Context* context, size_t srcHash, Optional<size_t> functionSourceIndex, const CodeCacheEntry& entry)
{
ASSERT(m_enabled && m_status == Status::READY);
ASSERT(m_cacheDirPath.length());
@ -396,11 +400,14 @@ void CodeCache::prepareCacheLoading(Context* context, size_t srcHash, const Code
m_status = Status::IN_PROGRESS;
m_currentContext.m_cacheFilePath = m_cacheDirPath + std::to_string(srcHash);
if (functionSourceIndex) {
m_currentContext.m_cacheFilePath += "." + std::to_string(functionSourceIndex.value());
}
m_currentContext.m_cacheEntry = entry;
m_currentContext.m_cacheStringTable = loadCacheStringTable(context);
}
void CodeCache::prepareCacheWriting(size_t srcHash)
void CodeCache::prepareCacheWriting(size_t srcHash, Optional<size_t> functionSourceIndex)
{
ASSERT(m_enabled && m_status == Status::READY);
ASSERT(m_cacheDirPath.length());
@ -410,6 +417,9 @@ void CodeCache::prepareCacheWriting(size_t srcHash)
m_status = Status::IN_PROGRESS;
m_currentContext.m_cacheFilePath = m_cacheDirPath + std::to_string(srcHash);
if (functionSourceIndex) {
m_currentContext.m_cacheFilePath += "." + std::to_string(functionSourceIndex.value());
}
m_currentContext.m_cacheStringTable = new CacheStringTable();
}
@ -430,7 +440,7 @@ bool CodeCache::postCacheLoading()
return false;
}
void CodeCache::postCacheWriting(size_t srcHash)
void CodeCache::postCacheWriting(size_t srcHash, Optional<size_t> functionSourceIndex)
{
ASSERT(m_enabled);
@ -441,7 +451,7 @@ void CodeCache::postCacheWriting(size_t srcHash)
// write time stamp
entry.m_lastWrittenTimeStamp = fastTickCount();
if (addCacheEntry(srcHash, m_currentContext.m_cacheEntry)) {
if (addCacheEntry(srcHash, functionSourceIndex, m_currentContext.m_cacheEntry)) {
if (writeCacheList()) {
reset();
m_status = Status::READY;
@ -694,7 +704,7 @@ bool CodeCache::writeCacheList()
while (entryCount < listSize) {
ASSERT(iter != m_cacheList.end());
CodeCacheEntryChunk entryChunk(iter->first, iter->second);
CodeCacheEntryChunk entryChunk(iter->first.m_srcHash, iter->first.m_functionSourceIndex, iter->second);
if (UNLIKELY(fwrite(&entryChunk, sizeof(CodeCacheEntryChunk), 1, listFile) != 1)) {
ESCARGOT_LOG_ERROR("[CodeCache] fwrite of %s failed\n", cacheListFilePath.data());
fclose(listFile);

View file

@ -26,12 +26,12 @@
#error "Code Cache does not support OS other than POSIX"
#endif
#if defined(ESCARGOT_ENABLE_TEST)
#define CODE_CACHE_MIN_SOURCE_LENGTH 1024
#define CODE_CACHE_MAX_CACHE_NUM 128
#else
#define CODE_CACHE_MIN_SOURCE_LENGTH 1024 * 4
#define CODE_CACHE_MAX_CACHE_NUM 32
#ifndef CODE_CACHE_MIN_SOURCE_LENGTH
#define CODE_CACHE_MIN_SOURCE_LENGTH 1024 * 32
#endif
#ifndef CODE_CACHE_MAX_CACHE_NUM
#define CODE_CACHE_MAX_CACHE_NUM 256
#endif
namespace Escargot {
@ -85,6 +85,43 @@ struct CodeCacheMetaInfo {
size_t dataSize;
};
struct CodeCacheItem {
size_t m_srcHash;
Optional<size_t> m_functionSourceIndex;
CodeCacheItem(size_t srcHash, Optional<size_t> functionSourceIndex)
: m_srcHash(srcHash)
, m_functionSourceIndex(functionSourceIndex)
{
}
bool operator==(const CodeCacheItem& src) const
{
return m_srcHash == src.m_srcHash && m_functionSourceIndex == src.m_functionSourceIndex;
}
};
} // namespace Escargot
namespace std {
template <>
struct hash<Escargot::CodeCacheItem> {
size_t operator()(Escargot::CodeCacheItem const& x) const
{
return x.m_srcHash;
}
};
template <>
struct equal_to<Escargot::CodeCacheItem> {
bool operator()(Escargot::CodeCacheItem const& a, Escargot::CodeCacheItem const& b) const
{
return a == b;
}
};
} // namespace std
namespace Escargot {
struct CodeCacheEntry {
CodeCacheEntry()
: m_lastWrittenTimeStamp(0)
@ -134,13 +171,15 @@ public:
{
}
CodeCacheEntryChunk(size_t srcHash, const CodeCacheEntry& entry)
CodeCacheEntryChunk(size_t srcHash, Optional<size_t> functionSourceIndex, const CodeCacheEntry& entry)
: m_srcHash(srcHash)
, m_functionSourceIndex(functionSourceIndex)
, m_entry(entry)
{
}
size_t m_srcHash;
Optional<size_t> m_functionSourceIndex;
CodeCacheEntry m_entry;
};
@ -148,12 +187,12 @@ public:
~CodeCache();
bool enabled() const { return m_enabled; }
std::pair<bool, CodeCacheEntry> searchCache(size_t srcHash);
std::pair<bool, CodeCacheEntry> searchCache(size_t srcHash, Optional<size_t> functionSourceIndex);
void prepareCacheLoading(Context* context, size_t srcHash, const CodeCacheEntry& entry);
void prepareCacheWriting(size_t srcHash);
void prepareCacheLoading(Context* context, size_t srcHash, Optional<size_t> functionSourceIndex, const CodeCacheEntry& entry);
void prepareCacheWriting(size_t srcHash, Optional<size_t> functionSourceIndex);
bool postCacheLoading();
void postCacheWriting(size_t srcHash);
void postCacheWriting(size_t srcHash, Optional<size_t> functionSourceIndex);
void storeStringTable();
void storeCodeBlockTree(InterpretedCodeBlock* topCodeBlock, CodeBlockCacheInfo* codeBlockCacheInfo);
@ -170,7 +209,7 @@ private:
CodeCacheContext m_currentContext; // current CodeCache infos
typedef std::unordered_map<size_t, CodeCacheEntry, std::hash<size_t>, std::equal_to<size_t>, std::allocator<std::pair<size_t const, CodeCacheEntry>>> CodeCacheListMap;
typedef std::unordered_map<CodeCacheItem, CodeCacheEntry, std::hash<CodeCacheItem>, std::equal_to<CodeCacheItem>, std::allocator<std::pair<CodeCacheItem const, CodeCacheEntry>>> CodeCacheListMap;
CodeCacheListMap m_cacheList;
CodeCacheWriter* m_cacheWriter;
@ -190,10 +229,10 @@ private:
void clearAll();
void reset();
void setCacheEntry(const CodeCacheEntryChunk& entryChunk);
bool addCacheEntry(size_t hash, const CodeCacheEntry& entry);
bool addCacheEntry(size_t hash, Optional<size_t> functionSourceIndex, const CodeCacheEntry& entry);
bool removeLRUCacheEntry();
bool removeCacheFile(size_t hash);
bool removeCacheFile(size_t hash, Optional<size_t> functionSourceIndex);
void storeCodeBlockTreeNode(InterpretedCodeBlock* codeBlock, size_t& nodeCount);
InterpretedCodeBlock* loadCodeBlockTreeNode(Script* script);

View file

@ -161,8 +161,17 @@ public:
}
};
Script(String* srcName, String* sourceCode, ModuleData* moduleData, bool canExecuteAgain, size_t originLineOffset)
Script(String* srcName, String* sourceCode, ModuleData* moduleData, size_t originLineOffset, bool canExecuteAgain
#if defined(ENABLE_CODE_CACHE)
,
bool isCacheable
#endif
)
: m_canExecuteAgain(canExecuteAgain && !moduleData)
#if defined(ENABLE_CODE_CACHE)
, m_isCacheable(isCacheable)
, m_sourceCodeHashValue(0)
#endif
, m_srcName(srcName)
, m_sourceCode(sourceCode)
, m_topCodeBlock(nullptr)
@ -204,6 +213,18 @@ public:
return m_moduleData;
}
#if defined(ENABLE_CODE_CACHE)
bool isCacheable()
{
return m_isCacheable;
}
size_t sourceCodeHashValue()
{
return m_sourceCodeHashValue;
}
#endif
size_t moduleRequestsLength();
String* moduleRequest(size_t i);
@ -297,6 +318,10 @@ private:
static Value asyncModuleRejectedFunction(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget);
bool m_canExecuteAgain;
#if defined(ENABLE_CODE_CACHE)
bool m_isCacheable;
size_t m_sourceCodeHashValue;
#endif
String* m_srcName;
String* m_sourceCode;
InterpretedCodeBlock* m_topCodeBlock;

View file

@ -308,14 +308,14 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* orig
if (cacheable) {
ASSERT(!parentCodeBlock);
srcHash = source->hashValue();
auto result = codeCache->searchCache(srcHash);
auto result = codeCache->searchCache(srcHash, Optional<size_t>());
if (result.first) {
GC_disable();
Script* script = new Script(srcName, source, nullptr, false, originLineOffset);
Script* script = new Script(srcName, source, nullptr, originLineOffset, false, cacheable);
CodeCacheEntry& entry = result.second;
codeCache->prepareCacheLoading(m_context, srcHash, entry);
codeCache->prepareCacheLoading(m_context, srcHash, Optional<size_t>(), entry);
// load CodeBlockTree
InterpretedCodeBlock* topCodeBlock = codeCache->loadCodeBlockTree(m_context, script);
// load global ByteCodeBlock
@ -368,7 +368,12 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* orig
programNode = esprima::parseProgram(m_context, sourceView, outerClassInfo,
isModule, strictFromOutside, inWith, allowSC, allowSP, allowNewTarget, allowArguments);
script = new Script(srcName, source, programNode->moduleData(), !parentCodeBlock, originLineOffset);
script = new Script(srcName, source, programNode->moduleData(), originLineOffset, !parentCodeBlock
#if defined(ENABLE_CODE_CACHE)
,
cacheable
#endif
);
if (parentCodeBlock) {
programNode->scopeContext()->m_hasEval = parentCodeBlock->hasEval();
programNode->scopeContext()->m_hasWith = parentCodeBlock->hasWith();
@ -421,7 +426,7 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* orig
#if defined(ENABLE_CODE_CACHE)
// Store cache
if (cacheable) {
codeCache->prepareCacheWriting(srcHash);
codeCache->prepareCacheWriting(srcHash, Optional<size_t>());
// For storing cache, CodeBlockTree is firstly saved
codeCache->storeCodeBlockTree(topCodeBlock, m_codeBlockCacheInfo);
@ -429,9 +434,11 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScript(String* orig
// After CodeBlockTree, ByteCode and StringTable are stored sequentially
topCodeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(m_context, topCodeBlock, programNode, inWith, true);
codeCache->postCacheWriting(srcHash);
codeCache->postCacheWriting(srcHash, Optional<size_t>());
deleteCodeBlockCacheInfo();
script->m_sourceCodeHashValue = srcHash;
ESCARGOT_LOG_INFO("[CodeCache] Store CodeCache Done (%s)\n", srcName->toUTF8StringData().data());
} else {
topCodeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(m_context, topCodeBlock, programNode, inWith, false);
@ -469,6 +476,36 @@ void ScriptParser::generateFunctionByteCode(ExecutionState& state, InterpretedCo
ASSERT(!m_context->debuggerEnabled() || !m_context->inDebuggingCodeMode());
#endif /* ESCARGOT_DEBUGGER */
#if defined(ENABLE_CODE_CACHE)
CodeCache* codeCache = m_context->vmInstance()->codeCache();
bool cacheable = codeBlock->script()->isCacheable() && codeBlock->src().length() > CODE_CACHE_MIN_SOURCE_LENGTH;
// Lode code from cache
size_t srcHash = codeBlock->script()->sourceCodeHashValue();
// Load cache
if (cacheable) {
auto result = codeCache->searchCache(srcHash, codeBlock->functionStart().index);
if (result.first) {
GC_disable();
CodeCacheEntry& entry = result.second;
codeCache->prepareCacheLoading(m_context, srcHash, codeBlock->functionStart().index, entry);
// load ByteCodeBlock
codeBlock->m_byteCodeBlock = codeCache->loadByteCodeBlock(m_context, codeBlock);
bool loadingDone = codeCache->postCacheLoading();
cacheable = loadingDone;
GC_enable();
if (LIKELY(loadingDone)) {
ESCARGOT_LOG_INFO("[CodeCache] Load CodeCache Done (%s: index %zu size %zu)\n", codeBlock->script()->srcName()->toNonGCUTF8StringData().data(),
codeBlock->functionStart().index, codeBlock->src().length());
return;
}
}
}
#endif
GC_disable();
FunctionNode* functionNode;
@ -489,7 +526,23 @@ void ScriptParser::generateFunctionByteCode(ExecutionState& state, InterpretedCo
// Generate ByteCode
try {
#if defined(ENABLE_CODE_CACHE)
// Store cache
if (cacheable) {
codeCache->prepareCacheWriting(srcHash, codeBlock->functionStart().index);
// After CodeBlockTree, ByteCode and StringTable are stored sequentially
codeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(state.context(), codeBlock, functionNode, false, true);
codeCache->postCacheWriting(srcHash, codeBlock->functionStart().index);
ESCARGOT_LOG_INFO("[CodeCache] Store CodeCache Done (%s: index %zu size %zu)\n", codeBlock->script()->srcName()->toNonGCUTF8StringData().data(),
codeBlock->functionStart().index, codeBlock->src().length());
} else {
codeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(state.context(), codeBlock, functionNode);
}
#else
codeBlock->m_byteCodeBlock = ByteCodeGenerator::generateByteCode(state.context(), codeBlock, functionNode);
#endif
} catch (const char* message) {
// reset ASTAllocator
m_context->astAllocator().reset();
@ -513,7 +566,12 @@ Script* ScriptParser::initializeJSONModule(String* source, String* srcName)
moduleData->m_localExportEntries.push_back(entry);
Script* script = new Script(srcName, source, moduleData, false, 0);
Script* script = new Script(srcName, source, moduleData, 0, false
#if defined(ENABLE_CODE_CACHE)
,
false
#endif
);
ModuleEnvironmentRecord* moduleRecord = new ModuleEnvironmentRecord(script);
moduleData->m_moduleRecord = moduleRecord;
@ -575,7 +633,12 @@ ScriptParser::InitializeScriptResult ScriptParser::initializeScriptWithDebugger(
programNode = esprima::parseProgram(m_context, sourceView, outerClassInfo, isModule, strictFromOutside, inWith, allowSC, allowSP, allowNewTarget, allowArguments);
script = new Script(srcName, source, programNode->moduleData(), !parentCodeBlock, originLineOffset);
script = new Script(srcName, source, programNode->moduleData(), originLineOffset, !parentCodeBlock
#if defined(ENABLE_CODE_CACHE)
,
false
#endif
);
if (parentCodeBlock) {
programNode->scopeContext()->m_hasEval = parentCodeBlock->hasEval();
programNode->scopeContext()->m_hasWith = parentCodeBlock->hasWith();

View file

@ -186,11 +186,6 @@ public:
m_inStrictMode = inStrictMode;
}
size_t stackLimit()
{
return 0;
}
void throwException(const Value& e);
bool hasRareData()