blob: aa4013f196193b6e3573a025b32369dd27aabc64 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "ParserPch.h"
#define ASSERT_THREAD() AssertMsg(mainThreadId == GetCurrentThreadContextId(), \
"Cannot use this member of BackgroundParser from thread other than the creating context's current thread")
#if ENABLE_NATIVE_CODEGEN
#if ENABLE_BACKGROUND_PARSING
BackgroundParser::BackgroundParser(Js::ScriptContext *scriptContext)
: JsUtil::WaitableJobManager(scriptContext->GetThreadContext()->GetJobProcessor()),
scriptContext(scriptContext),
unprocessedItemsHead(nullptr),
unprocessedItemsTail(nullptr),
failedBackgroundParseItem(nullptr),
pendingBackgroundItems(0)
{
Processor()->AddManager(this);
#if DBG
this->mainThreadId = GetCurrentThreadContextId();
#endif
}
BackgroundParser::~BackgroundParser()
{
JsUtil::JobProcessor *processor = Processor();
if (processor->ProcessesInBackground())
{
static_cast<JsUtil::BackgroundJobProcessor*>(processor)->IterateBackgroundThreads([&](JsUtil::ParallelThreadData *threadData)->bool {
if (threadData->parser)
{
// Adelete to make sure dtor are called (since the HashTbl has its NoReleaseAllocator)
Adelete(threadData->threadArena, threadData->parser);
threadData->parser = nullptr;
}
return false;
});
}
processor->RemoveManager(this);
}
BackgroundParser * BackgroundParser::New(Js::ScriptContext *scriptContext)
{
return HeapNew(BackgroundParser, scriptContext);
}
void BackgroundParser::Delete(BackgroundParser *backgroundParser)
{
HeapDelete(backgroundParser);
}
bool BackgroundParser::Process(JsUtil::Job *const job, JsUtil::ParallelThreadData *threadData)
{
BackgroundParseItem *backgroundItem = static_cast<BackgroundParseItem*>(job);
if (failedBackgroundParseItem)
{
if (backgroundItem->GetParseNode()->ichMin > failedBackgroundParseItem->GetParseNode()->ichMin)
{
return true;
}
}
if (threadData->parser == nullptr || threadData->canDecommit)
{
if (threadData->parser != nullptr)
{
// "canDecommit" means the previous parse finished.
// Don't leave a parser with stale state in the thread data, or we'll mess up the bindings.
threadData->backgroundPageAllocator.DecommitNow();
this->OnDecommit(threadData);
}
threadData->canDecommit = false;
// Lazily create a parser instance for this thread from the thread's page allocator.
// It will stay around until the main thread's current parser instance goes away, which will free
// the background thread to decommit its pages.
threadData->parser = Anew(threadData->threadArena, Parser, this->scriptContext, backgroundItem->IsStrictMode(), &threadData->backgroundPageAllocator, true);
threadData->pse = Anew(threadData->threadArena, CompileScriptException);
}
Parser *parser = threadData->parser;
return this->Process(backgroundItem, parser, threadData->pse);
}
bool BackgroundParser::Process(JsUtil::Job *const job, Parser *parser, CompileScriptException *pse)
{
BackgroundParseItem *backgroundItem = static_cast<BackgroundParseItem*>(job);
Assert(parser->GetCurrBackgroundParseItem() == nullptr);
parser->SetCurrBackgroundParseItem(backgroundItem);
backgroundItem->SetParser(parser);
HRESULT hr = parser->ParseFunctionInBackground(backgroundItem->GetParseNode(), backgroundItem->GetParseContext(), backgroundItem->IsDeferred(), pse);
backgroundItem->SetMaxBlockId(parser->GetLastBlockId());
backgroundItem->SetHR(hr);
if (FAILED(hr))
{
backgroundItem->SetPSE(pse);
}
backgroundItem->SetCompleted(true);
parser->SetCurrBackgroundParseItem(nullptr);
return hr == S_OK;
}
void BackgroundParser::JobProcessed(JsUtil::Job *const job, const bool succeeded)
{
// This is called from inside a lock, so we can mess with background parser attributes.
BackgroundParseItem *backgroundItem = static_cast<BackgroundParseItem*>(job);
this->RemoveFromUnprocessedItems(backgroundItem);
--this->pendingBackgroundItems;
if (!succeeded)
{
Assert(FAILED(backgroundItem->GetHR()) || failedBackgroundParseItem);
if (FAILED(backgroundItem->GetHR()))
{
if (!failedBackgroundParseItem)
{
failedBackgroundParseItem = backgroundItem;
}
else
{
// If syntax errors are detected on multiple threads, the lexically earlier one should win.
CompileScriptException *newPse = backgroundItem->GetPSE();
CompileScriptException *oldPse = failedBackgroundParseItem->GetPSE();
if (newPse->line < oldPse->line ||
(newPse->line == oldPse->line && newPse->ichMinLine < oldPse->ichMinLine))
{
failedBackgroundParseItem = backgroundItem;
}
}
}
}
}
void BackgroundParser::OnDecommit(JsUtil::ParallelThreadData *threadData)
{
if (threadData->parser)
{
// Adelete to make sure dtor are called (since the HashTbl has its NoReleaseAllocator)
Adelete(threadData->threadArena, threadData->parser);
threadData->parser = nullptr;
}
}
BackgroundParseItem * BackgroundParser::NewBackgroundParseItem(Parser *parser, ParseNodeFnc *parseNode, bool isDeferred)
{
BackgroundParseItem *item = Anew(parser->GetAllocator(), BackgroundParseItem, this, parser, parseNode, isDeferred);
parser->AddBackgroundParseItem(item);
return item;
}
bool BackgroundParser::ParseBackgroundItem(Parser *parser, ParseNodeFnc *parseNode, bool isDeferred)
{
ASSERT_THREAD();
AutoPtr<BackgroundParseItem> workItemAutoPtr(this->NewBackgroundParseItem(parser, parseNode, isDeferred));
if ((BackgroundParseItem*) workItemAutoPtr == nullptr)
{
// OOM, just skip this work item and return.
// TODO: Raise an OOM parse-time exception.
return false;
}
parser->PrepareForBackgroundParse();
BackgroundParseItem * backgroundItem = workItemAutoPtr.Detach();
this->AddToParseQueue(backgroundItem, false, this->Processor()->ProcessesInBackground());
return true;
}
BackgroundParseItem *BackgroundParser::GetJob(BackgroundParseItem *workitem) const
{
return workitem;
}
bool BackgroundParser::WasAddedToJobProcessor(JsUtil::Job *const job) const
{
ASSERT_THREAD();
Assert(job);
return static_cast<BackgroundParseItem*>(job)->IsInParseQueue();
}
void BackgroundParser::BeforeWaitForJob(BackgroundParseItem *const item) const
{
}
void BackgroundParser::AfterWaitForJob(BackgroundParseItem *const item) const
{
}
void BackgroundParser::AddToParseQueue(BackgroundParseItem *const item, bool prioritize, bool lock)
{
AutoOptionalCriticalSection autoLock(lock ? Processor()->GetCriticalSection() : nullptr);
++this->pendingBackgroundItems;
Processor()->AddJob(item, prioritize); // This one can throw (really unlikely though), OOM specifically.
this->AddUnprocessedItem(item);
item->OnAddToParseQueue();
}
void BackgroundParser::AddUnprocessedItem(BackgroundParseItem *const item)
{
if (this->unprocessedItemsTail == nullptr)
{
this->unprocessedItemsHead = item;
}
else
{
this->unprocessedItemsTail->SetNextUnprocessedItem(item);
}
item->SetPrevUnprocessedItem(this->unprocessedItemsTail);
this->unprocessedItemsTail = item;
}
void BackgroundParser::RemoveFromUnprocessedItems(BackgroundParseItem *const item)
{
if (this->unprocessedItemsHead == item)
{
this->unprocessedItemsHead = item->GetNextUnprocessedItem();
}
else
{
item->GetPrevUnprocessedItem()->SetNextUnprocessedItem(item->GetNextUnprocessedItem());
}
if (this->unprocessedItemsTail == item)
{
this->unprocessedItemsTail = item->GetPrevUnprocessedItem();
}
else
{
item->GetNextUnprocessedItem()->SetPrevUnprocessedItem(item->GetPrevUnprocessedItem());
}
item->SetNextUnprocessedItem(nullptr);
item->SetPrevUnprocessedItem(nullptr);
}
BackgroundParseItem *BackgroundParser::GetNextUnprocessedItem() const
{
BackgroundParseItem *item;
bool background = this->Processor()->ProcessesInBackground();
for (item = this->unprocessedItemsHead; item; item = item->GetNextUnprocessedItem())
{
if (!background || !static_cast<JsUtil::BackgroundJobProcessor*>(Processor())->IsBeingProcessed(item))
{
return item;
}
}
return nullptr;
}
BackgroundParseItem::BackgroundParseItem(JsUtil::JobManager *const manager, Parser *const parser, ParseNodeFnc *parseNode, bool defer)
: JsUtil::Job(manager),
maxBlockId((uint)-1),
strictMode(parser->IsStrictMode()),
parseNode(parseNode),
parser(nullptr),
nextItem(nullptr),
nextUnprocessedItem(nullptr),
prevUnprocessedItem(nullptr),
pse(nullptr),
regExpNodes(nullptr),
completed(false),
inParseQueue(false),
isDeferred(defer)
{
parser->CaptureContext(&parseContext);
}
void BackgroundParseItem::OnAddToParseQueue()
{
this->inParseQueue = true;
}
void BackgroundParseItem::OnRemoveFromParseQueue()
{
this->inParseQueue = false;
}
void BackgroundParseItem::AddRegExpNode(ParseNode *const pnode, ArenaAllocator *alloc)
{
if (regExpNodes == nullptr)
{
regExpNodes = Anew(alloc, NodeDList, alloc);
}
regExpNodes->Append(pnode);
}
#endif
#endif