Преглед изворни кода

move dynamic interpreter thunk generation OOP

Michael Holman пре 9 година
родитељ
комит
b54bd3d69a

+ 192 - 53
lib/Backend/InterpreterThunkEmitter.cpp

@@ -228,15 +228,26 @@ const BYTE InterpreterThunkEmitter::HeaderSize = sizeof(InterpreterThunk);
 const BYTE InterpreterThunkEmitter::ThunkSize = sizeof(Call);
 const uint InterpreterThunkEmitter::ThunksPerBlock = (BlockSize - HeaderSize) / ThunkSize;
 
-InterpreterThunkEmitter::InterpreterThunkEmitter(ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, bool isAsmInterpreterThunk) :
-    // TODO: michhol oop JIT move interpreter thunk emitter out of process
-    emitBufferManager(allocator, codePageAllocators, /*scriptContext*/ nullptr, _u("Interpreter thunk buffer"), GetCurrentProcess()),
-    allocation(nullptr),
+InterpreterThunkEmitter::InterpreterThunkEmitter(Js::ScriptContext* context, ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, bool isAsmInterpreterThunk) :
+    emitBufferManager(nullptr),
+    scriptContext(context),
     allocator(allocator),
     thunkCount(0),
     thunkBuffer(nullptr),
     isAsmInterpreterThunk(isAsmInterpreterThunk)
 {
+    if (!JITManager::GetJITManager()->IsOOPJITEnabled())
+    {
+        emitBufferManager = HeapNew(EmitBufferManager<>, allocator, codePageAllocators, /*scriptContext*/ nullptr, _u("Interpreter thunk buffer"), GetCurrentProcess());
+    }
+}
+
+InterpreterThunkEmitter::~InterpreterThunkEmitter()
+{
+    if (emitBufferManager != nullptr)
+    {
+        HeapDelete(emitBufferManager);
+    }
 }
 
 //
@@ -287,40 +298,110 @@ void* InterpreterThunkEmitter::ConvertToEntryPoint(PVOID dynamicInterpreterThunk
 
 void InterpreterThunkEmitter::NewThunkBlock()
 {
-    Assert(this->thunkCount == 0);
-    BYTE* buffer;
-    BYTE* currentBuffer;
-    DWORD bufferSize = BlockSize;
-    DWORD thunkCount = 0;
-
-    void * interpreterThunk = nullptr;
-
-    // the static interpreter thunk invoked by the dynamic emitted thunk
-#ifdef ASMJS_PLAT
-    if (isAsmInterpreterThunk)
+#ifdef ENABLE_OOP_NATIVE_CODEGEN
+    if (JITManager::GetJITManager()->IsOOPJITEnabled())
     {
-        interpreterThunk = (void*)Js::InterpreterStackFrame::InterpreterAsmThunk;
+        NewOOPJITThunkBlock();
+        return;
     }
-    else
 #endif
-    {
-        interpreterThunk = (void*)Js::InterpreterStackFrame::InterpreterThunk;
-    }
 
-    allocation = emitBufferManager.AllocateBuffer(bufferSize, &buffer);
+    Assert(this->thunkCount == 0);
+    BYTE* buffer;
+
+    EmitBufferAllocation * allocation = emitBufferManager->AllocateBuffer(BlockSize, &buffer);
     if (allocation == nullptr) 
     {
         Js::Throw::OutOfMemory();
     }
-    if (!emitBufferManager.ProtectBufferWithExecuteReadWriteForInterpreter(allocation))
+    if (!emitBufferManager->ProtectBufferWithExecuteReadWriteForInterpreter(allocation))
+    {
+        Js::Throw::OutOfMemory();
+    }
+
+#if PDATA_ENABLED
+    PRUNTIME_FUNCTION pdataStart;
+    intptr_t epilogEnd;
+#endif
+
+    FillBuffer(
+        this->allocator,
+        this->scriptContext->GetThreadContext(),
+        this->isAsmInterpreterThunk,
+        (intptr_t)buffer,
+        BlockSize,
+        buffer,
+#if PDATA_ENABLED
+        &pdataStart,
+        &epilogEnd,
+#endif
+        &this->thunkCount
+    );
+
+    if (!emitBufferManager->CommitReadWriteBufferForInterpreter(allocation, buffer, BlockSize))
     {
         Js::Throw::OutOfMemory();
     }
 
-    currentBuffer = buffer;
+    // Call to set VALID flag for CFG check
+    ThreadContext::GetContextForCurrentThread()->SetValidCallTargetForCFG(buffer);
+
+    // Update object state only at the end when everything has succeeded - and no exceptions can be thrown.
+    auto block = this->thunkBlocks.PrependNode(allocator, buffer);
+#if PDATA_ENABLED
+    void* pdataTable;
+    PDataManager::RegisterPdata((PRUNTIME_FUNCTION)pdataStart, (ULONG_PTR)buffer, (ULONG_PTR)epilogEnd, &pdataTable);
+    block->SetPdata(pdataTable);
+#else
+    Unused(block);
+#endif
+    this->thunkBuffer = buffer;
+}
+
+#ifdef ENABLE_OOP_NATIVE_CODEGEN
+void InterpreterThunkEmitter::NewOOPJITThunkBlock()
+{
+    InterpreterThunkInfoIDL thunkInfo;
+    HRESULT hr = JITManager::GetJITManager()->NewInterpreterThunkBlock(
+        this->scriptContext->GetRemoteScriptAddr(),
+        this->isAsmInterpreterThunk,
+        &thunkInfo
+    );
+    JITManager::HandleServerCallResult(hr);
 
+    this->thunkBuffer = (BYTE*)thunkInfo.thunkBlockAddr;
+
+    // Update object state only at the end when everything has succeeded - and no exceptions can be thrown.
+    auto block = this->thunkBlocks.PrependNode(allocator, this->thunkBuffer);
+#if PDATA_ENABLED
+    void* pdataTable;
+    PDataManager::RegisterPdata((PRUNTIME_FUNCTION)thunkInfo.pdataTableStart, (ULONG_PTR)this->thunkBuffer, (ULONG_PTR)thunkInfo.epilogEndAddr, &pdataTable);
+    block->SetPdata(pdataTable);
+#else
+    Unused(block);
+#endif
+
+    this->thunkCount = thunkInfo.thunkCount;
+}
+#endif
+
+/* static */
+void InterpreterThunkEmitter::FillBuffer(
+    _In_ ArenaAllocator * arena,
+    _In_ ThreadContextInfo * threadContext,
+    _In_ bool asmJsThunk,
+    _In_ intptr_t finalAddr,
+    _In_ size_t bufferSize,
+    _Out_writes_bytes_all_(bufferSize) BYTE* buffer,
+#if PDATA_ENABLED
+    _Out_ PRUNTIME_FUNCTION * pdataTableStart,
+    _Out_ intptr_t * epilogEndAddr,
+#endif
+    _Out_ DWORD * thunkCount
+    )
+{
 #ifdef _M_X64
-    PrologEncoder prologEncoder(allocator);
+    PrologEncoder prologEncoder(arena);
     prologEncoder.EncodeSmallProlog(PrologSize, StackAllocSize);
     DWORD pdataSize = prologEncoder.SizeOfPData();
 #elif defined(_M_ARM32_OR_ARM64)
@@ -328,23 +409,43 @@ void InterpreterThunkEmitter::NewThunkBlock()
 #else
     DWORD pdataSize = 0;
 #endif
-    DWORD bytesRemaining = bufferSize;
+    DWORD bytesRemaining = BlockSize;
     DWORD bytesWritten = 0;
     DWORD epilogSize = sizeof(Epilog);
+    DWORD thunks = 0;
+
+    intptr_t interpreterThunk;
+
+    // the static interpreter thunk invoked by the dynamic emitted thunk
+#ifdef ASMJS_PLAT
+    if (asmJsThunk)
+    {
+        interpreterThunk = SHIFT_ADDR(threadContext, &Js::InterpreterStackFrame::InterpreterAsmThunk);
+    }
+    else
+#endif
+    {
+        interpreterThunk = SHIFT_ADDR(threadContext, &Js::InterpreterStackFrame::InterpreterThunk);
+    }
 
+    BYTE * currentBuffer = buffer;
     // Ensure there is space for PDATA at the end
-    BYTE* pdataStart = currentBuffer + (bufferSize - Math::Align(pdataSize, EMIT_BUFFER_ALIGNMENT));
+    BYTE* pdataStart = currentBuffer + (BlockSize - Math::Align(pdataSize, EMIT_BUFFER_ALIGNMENT));
     BYTE* epilogStart = pdataStart - Math::Align(epilogSize, EMIT_BUFFER_ALIGNMENT);
 
+    // Ensure there is space for PDATA at the end
+    intptr_t finalPdataStart = finalAddr + (BlockSize - Math::Align(pdataSize, EMIT_BUFFER_ALIGNMENT));
+    intptr_t finalEpilogStart = finalPdataStart - Math::Align(epilogSize, EMIT_BUFFER_ALIGNMENT);
+
     // Copy the thunk buffer and modify it.
     js_memcpy_s(currentBuffer, bytesRemaining, InterpreterThunk, HeaderSize);
-    EncodeInterpreterThunk(currentBuffer, buffer, HeaderSize, epilogStart, epilogSize, interpreterThunk);
+    EncodeInterpreterThunk(currentBuffer, finalAddr, HeaderSize, finalEpilogStart, epilogSize, interpreterThunk);
     currentBuffer += HeaderSize;
     bytesRemaining -= HeaderSize;
 
     // Copy call buffer
     DWORD callSize = sizeof(Call);
-    while(currentBuffer < epilogStart - callSize)
+    while (currentBuffer < epilogStart - callSize)
     {
         js_memcpy_s(currentBuffer, bytesRemaining, Call, callSize);
 #if _M_ARM
@@ -367,7 +468,7 @@ void InterpreterThunkEmitter::NewThunkBlock()
 #endif
         currentBuffer += callSize;
         bytesRemaining -= callSize;
-        thunkCount++;
+        thunks++;
     }
 
     // Fill any gap till start of epilog
@@ -394,30 +495,20 @@ void InterpreterThunkEmitter::NewThunkBlock()
     GeneratePdata(buffer, functionSize, &pdata);
     bytesWritten = CopyWithAlignment(pdataStart, bytesRemaining, (const BYTE*)&pdata, pdataSize, EMIT_BUFFER_ALIGNMENT);
 #endif
-    void* pdataTable;
-    PDataManager::RegisterPdata((PRUNTIME_FUNCTION) pdataStart, (ULONG_PTR) buffer, (ULONG_PTR) epilogEnd, &pdataTable);
+    *pdataTableStart = (PRUNTIME_FUNCTION)finalPdataStart;
+    *epilogEndAddr = finalEpilogStart;
 #endif
-    if (!emitBufferManager.CommitReadWriteBufferForInterpreter(allocation, buffer, bufferSize))
-    {
-        Js::Throw::OutOfMemory();
-    }
-
-    // Call to set VALID flag for CFG check
-    ThreadContext::GetContextForCurrentThread()->SetValidCallTargetForCFG(buffer);
-
-    // Update object state only at the end when everything has succeeded - and no exceptions can be thrown.
-    ThunkBlock* block = this->thunkBlocks.PrependNode(allocator, buffer);
-    UNREFERENCED_PARAMETER(block);
-#if PDATA_ENABLED
-    block->SetPdata(pdataTable);
-#endif
-    this->thunkCount = thunkCount;
-    this->thunkBuffer = buffer;
+    *thunkCount = thunks;
 }
 
-
 #if _M_ARM
-void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk)
+void InterpreterThunkEmitter::EncodeInterpreterThunk(
+    __in_bcount(thunkSize) BYTE* thunkBuffer,
+    __in const intptr_t thunkBufferStartAddress,
+    __in const DWORD thunkSize,
+    __in const intptr_t epilogStart,
+    __in const DWORD epilogSize,
+    __in const intptr_t interpreterThunk)
 {
     _Analysis_assume_(thunkSize == HeaderSize);
     // Encode MOVW
@@ -482,7 +573,13 @@ void InterpreterThunkEmitter::GeneratePdata(_In_ const BYTE* entryPoint, _In_ co
 }
 
 #elif _M_ARM64
-void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk)
+void InterpreterThunkEmitter::EncodeInterpreterThunk(
+    __in_bcount(thunkSize) BYTE* thunkBuffer,
+    __in const intptr_t thunkBufferStartAddress,
+    __in const DWORD thunkSize,
+    __in const intptr_t epilogStart,
+    __in const DWORD epilogSize,
+    __in const intptr_t interpreterThunk)
 {
     int addrOffset = ThunkAddressOffset;
 
@@ -556,7 +653,13 @@ void InterpreterThunkEmitter::GeneratePdata(_In_ const BYTE* entryPoint, _In_ co
     function->FrameSize = 5;                    // the number of bytes of stack that is allocated for this function divided by 16
 }
 #else
-void InterpreterThunkEmitter::EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk)
+void InterpreterThunkEmitter::EncodeInterpreterThunk(
+    __in_bcount(thunkSize) BYTE* thunkBuffer,
+    __in const intptr_t thunkBufferStartAddress,
+    __in const DWORD thunkSize,
+    __in const intptr_t epilogStart,
+    __in const DWORD epilogSize,
+    __in const intptr_t interpreterThunk)
 {
     _Analysis_assume_(thunkSize == HeaderSize);
     Emit(thunkBuffer, ThunkAddressOffset, (uintptr_t)interpreterThunk);
@@ -579,7 +682,6 @@ DWORD InterpreterThunkEmitter::FillDebugBreak(_In_ BYTE* dest, _In_ DWORD count)
 #elif defined(_M_ARM64)
     Assert(count % 4 == 0);
 #endif
-    // TODO: michhol OOP JIT. after mving OOP, change to runtime process handle
     CustomHeap::FillDebugBreak(dest, count, GetCurrentProcess());
     return count;
 }
@@ -607,6 +709,30 @@ DWORD InterpreterThunkEmitter::CopyWithAlignment(
     return srcSize;
 }
 
+#if DBG
+bool
+InterpreterThunkEmitter::IsInHeap(void* address)
+{
+#ifdef ENABLE_OOP_NATIVE_CODEGEN
+    if (JITManager::GetJITManager()->IsOOPJITEnabled())
+    {
+        intptr_t remoteScript = this->scriptContext->GetRemoteScriptAddr(false);
+        if (!remoteScript)
+        {
+            return false;
+        }
+        boolean result;
+        JITManager::GetJITManager()->IsInterpreterThunkAddr(remoteScript, (intptr_t)address, this->isAsmInterpreterThunk, &result);
+        return result != FALSE;
+    }
+    else
+#endif
+    {
+        return emitBufferManager->IsInHeap(address);
+    }
+}
+#endif
+
 // We only decommit at close because there might still be some
 // code running here.
 // The destructor of emitBufferManager will cause the eventual release.
@@ -622,7 +748,20 @@ void InterpreterThunkEmitter::Close()
 #endif
     this->thunkBlocks.Clear(allocator);
     this->freeListedThunkBlocks.Clear(allocator);
-    emitBufferManager.Decommit();
+#ifdef ENABLE_OOP_NATIVE_CODEGEN
+    if (JITManager::GetJITManager()->IsOOPJITEnabled())
+    {
+        intptr_t remoteScript = this->scriptContext->GetRemoteScriptAddr(false);
+        if (remoteScript)
+        {
+            JITManager::GetJITManager()->DecommitInterpreterBufferManager(remoteScript, this->isAsmInterpreterThunk);
+        }
+    }
+    else
+#endif
+    {
+        emitBufferManager->Decommit();
+    }
     this->thunkBuffer = nullptr;
     this->thunkCount = 0;
 }

+ 38 - 15
lib/Backend/InterpreterThunkEmitter.h

@@ -57,13 +57,13 @@ class InterpreterThunkEmitter
 {
 private:
     /* ------- instance methods --------*/
-    EmitBufferManager<> emitBufferManager;
-    EmitBufferAllocation *allocation;
+    EmitBufferManager<> * emitBufferManager;
     SListBase<ThunkBlock> thunkBlocks;
     SListBase<ThunkBlock> freeListedThunkBlocks;
     bool isAsmInterpreterThunk; // To emit address of InterpreterAsmThunk or InterpreterThunk
     BYTE*                thunkBuffer;
     ArenaAllocator*      allocator;
+    Js::ScriptContext *  scriptContext;
     DWORD thunkCount;                      // Count of thunks available in the current thunk block
 
     /* -------static constants ----------*/
@@ -94,10 +94,21 @@ private:
 
     /* ------private helpers -----------*/
     void NewThunkBlock();
-    void EncodeInterpreterThunk(__in_bcount(thunkSize) BYTE* thunkBuffer, __in_bcount(thunkSize) BYTE* thunkBufferStartAddress, __in const DWORD thunkSize, __in_bcount(epilogSize) BYTE* epilogStart, __in const DWORD epilogSize, __in void * const interpreterThunk);
+
+#ifdef ENABLE_OOP_NATIVE_CODEGEN
+    void NewOOPJITThunkBlock();
+#endif
+
+    static void EncodeInterpreterThunk(
+        __in_bcount(thunkSize) BYTE* thunkBuffer,
+        __in const intptr_t thunkBufferStartAddress,
+        __in const DWORD thunkSize,
+        __in const intptr_t epilogStart,
+        __in const DWORD epilogSize,
+        __in const intptr_t interpreterThunk);
 #if defined(_M_ARM32_OR_ARM64)
-    DWORD EncodeMove(DWORD opCode, int reg, DWORD imm16);
-    void GeneratePdata(_In_ const BYTE* entryPoint, _In_ const DWORD functionSize, _Out_ RUNTIME_FUNCTION* function);
+    static DWORD EncodeMove(DWORD opCode, int reg, DWORD imm16);
+    static void GeneratePdata(_In_ const BYTE* entryPoint, _In_ const DWORD functionSize, _Out_ RUNTIME_FUNCTION* function);
 #endif
 
     /*-------static helpers ---------*/
@@ -109,6 +120,8 @@ private:
         AssertMsg(*(T*) (dest + offset) == 0, "Overwriting an already existing opcode?");
         *(T*)(dest + offset) = value;
     };
+
+    BYTE* AllocateFromFreeList(PVOID* ppDynamicInterpreterThunk);
 public:
     static const BYTE HeaderSize;
     static const BYTE ThunkSize;
@@ -116,24 +129,34 @@ public:
     static const uint BlockSize;
     static void* ConvertToEntryPoint(PVOID dynamicInterpreterThunk);
 
-    InterpreterThunkEmitter(ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, bool isAsmInterpreterThunk = false);
-
+    InterpreterThunkEmitter(Js::ScriptContext * context, ArenaAllocator* allocator, CustomHeap::CodePageAllocators * codePageAllocators, bool isAsmInterpreterThunk = false);
+    ~InterpreterThunkEmitter();
     BYTE* GetNextThunk(PVOID* ppDynamicInterpreterThunk);
 
-    BYTE* AllocateFromFreeList(PVOID* ppDynamicInterpreterThunk);
-
     void Close();
     void Release(BYTE* thunkAddress, bool addtoFreeList);
-
     // Returns true if the argument falls within the range managed by this buffer.
-    inline bool IsInHeap(void* address)
-    {
-        return emitBufferManager.IsInHeap(address);
-    }
+#if DBG
+    bool IsInHeap(void* address);
+#endif
     const EmitBufferManager<>* GetEmitBufferManager() const
     {
-        return &emitBufferManager;
+        return emitBufferManager;
     }
 
+    static void FillBuffer(
+        _In_ ArenaAllocator * arena,
+        _In_ ThreadContextInfo * context,
+        _In_ bool asmJsThunk,
+        _In_ intptr_t finalAddr,
+        _In_ size_t bufferSize,
+        _Out_writes_bytes_all_(bufferSize) BYTE* buffer,
+#if PDATA_ENABLED
+        _Out_ PRUNTIME_FUNCTION * pdataTableStart,
+        _Out_ intptr_t * epilogEndAddr,
+#endif
+        _Out_ DWORD * thunkCount
+    );
+
 };
 #endif

+ 40 - 0
lib/Backend/ServerScriptContext.cpp

@@ -12,6 +12,9 @@ ServerScriptContext::ServerScriptContext(ScriptContextDataIDL * contextData, Ser
     m_contextData(*contextData),
     threadContextInfo(threadContextInfo),
     m_isPRNGSeeded(false),
+    m_interpreterThunkBufferManager(nullptr),
+    m_asmJsInterpreterThunkBufferManager(nullptr),
+    m_sourceCodeArena(_u("JITSourceCodeArena"), threadContextInfo->GetForegroundPageAllocator(), Js::Throw::OutOfMemory),
     m_domFastPathHelperMap(nullptr),
     m_moduleRecords(&HeapAllocator::Instance),
     m_globalThisAddr(0),
@@ -44,6 +47,14 @@ ServerScriptContext::~ServerScriptContext()
         HeapDelete(m_codeGenProfiler);
     }
 #endif
+    if (m_asmJsInterpreterThunkBufferManager)
+    {
+        HeapDelete(m_asmJsInterpreterThunkBufferManager);
+    }
+    if (m_interpreterThunkBufferManager)
+    {
+        HeapDelete(m_interpreterThunkBufferManager);
+    }
 }
 
 intptr_t
@@ -271,6 +282,35 @@ ServerScriptContext::AddToDOMFastPathHelperMap(intptr_t funcInfoAddr, IR::JnHelp
     m_domFastPathHelperMap->Add(funcInfoAddr, helper);
 }
 
+ArenaAllocator *
+ServerScriptContext::GetSourceCodeArena()
+{
+    return &m_sourceCodeArena;
+}
+
+void
+ServerScriptContext::DecommitEmitBufferManager(bool asmJsManager)
+{
+    EmitBufferManager<> * manager = GetEmitBufferManager(asmJsManager);
+    if (manager != nullptr)
+    {
+        manager->Decommit();
+    }
+}
+
+EmitBufferManager<> *
+ServerScriptContext::GetEmitBufferManager(bool asmJsManager)
+{
+    if (asmJsManager)
+    {
+        return m_asmJsInterpreterThunkBufferManager;
+    }
+    else
+    {
+        return m_interpreterThunkBufferManager;
+    }
+}
+
 IR::JnHelperMethod
 ServerScriptContext::GetDOMFastPathHelper(intptr_t funcInfoAddr)
 {

+ 7 - 1
lib/Backend/ServerScriptContext.h

@@ -61,10 +61,12 @@ public:
     void SetIsPRNGSeeded(bool value);
     void AddModuleRecordInfo(unsigned int moduleId, __int64 localExportSlotsAddr);
     void UpdateGlobalObjectThisAddr(intptr_t globalThis);
-
+    EmitBufferManager<> * GetEmitBufferManager(bool asmJsManager);
+    void DecommitEmitBufferManager(bool asmJsManager);
     Js::ScriptContextProfiler *  GetCodeGenProfiler() const;
     ServerThreadContext* GetThreadContext() { return threadContextInfo; }
 
+    ArenaAllocator * GetSourceCodeArena();
     void Close();
     void AddRef();
     void Release();
@@ -74,6 +76,10 @@ private:
 #ifdef PROFILE_EXEC
     Js::ScriptContextProfiler * m_codeGenProfiler;
 #endif
+    ArenaAllocator m_sourceCodeArena;
+
+    EmitBufferManager<> * m_interpreterThunkBufferManager;
+    EmitBufferManager<> * m_asmJsInterpreterThunkBufferManager;
 
     ScriptContextDataIDL m_contextData;
     intptr_t m_globalThisAddr;

+ 22 - 0
lib/Backend/ServerThreadContext.cpp

@@ -16,7 +16,15 @@ ServerThreadContext::ServerThreadContext(ThreadContextDataIDL * data) :
     m_pageAllocs(&HeapAllocator::Instance),
     m_preReservedVirtualAllocator((HANDLE)data->processHandle),
     m_codePageAllocators(&m_policyManager, ALLOC_XDATA, &m_preReservedVirtualAllocator, (HANDLE)data->processHandle),
+#if DYNAMIC_INTERPRETER_THUNK || defined(ASMJS_PLAT)
+    m_thunkPageAllocators(&m_policyManager, /* allocXData */ false, /* virtualAllocator */ nullptr, (HANDLE)data->processHandle),
+#endif
     m_codeGenAlloc(&m_policyManager, nullptr, &m_codePageAllocators, (HANDLE)data->processHandle),
+    m_pageAlloc(&m_policyManager, Js::Configuration::Global.flags, PageAllocatorType_BGJIT,
+        AutoSystemInfo::Data.IsLowMemoryProcess() ?
+            PageAllocator::DefaultLowMaxFreePageCount :
+            PageAllocator::DefaultMaxFreePageCount
+    ),
     // TODO: OOP JIT, don't hardcode name
 #ifdef NTBUILD
     m_jitChakraBaseAddress((intptr_t)GetModuleHandle(_u("Chakra.dll"))),
@@ -167,6 +175,14 @@ ServerThreadContext::GetProcessHandle() const
     return reinterpret_cast<HANDLE>(m_threadContextData.processHandle);
 }
 
+#if DYNAMIC_INTERPRETER_THUNK || defined(ASMJS_PLAT)
+CustomHeap::CodePageAllocators *
+ServerThreadContext::GetThunkPageAllocators()
+{
+    return &m_thunkPageAllocators;
+}
+#endif
+
 CustomHeap::CodePageAllocators *
 ServerThreadContext::GetCodePageAllocators()
 {
@@ -197,6 +213,12 @@ ServerThreadContext::GetRuntimeCRTBaseAddress() const
     return static_cast<intptr_t>(m_threadContextData.crtBaseAddress);
 }
 
+PageAllocator *
+ServerThreadContext::GetForegroundPageAllocator()
+{
+    return &m_pageAlloc;
+}
+
 Js::PropertyRecord const *
 ServerThreadContext::GetPropertyRecord(Js::PropertyId propertyId)
 {

+ 9 - 2
lib/Backend/ServerThreadContext.h

@@ -45,11 +45,13 @@ public:
     void RemoveFromPropertyMap(Js::PropertyId reclaimedId);
     void AddToPropertyMap(const Js::PropertyRecord * propertyRecord);
     void SetWellKnownHostTypeId(Js::TypeId typeId) { this->wellKnownHostTypeHTMLAllCollectionTypeId = typeId; }
-
+#if DYNAMIC_INTERPRETER_THUNK || defined(ASMJS_PLAT)
+    CustomHeap::CodePageAllocators * GetThunkPageAllocators();
+#endif
     void AddRef();
     void Release();
     void Close();
-
+    PageAllocator * GetForegroundPageAllocator();
 #ifdef STACK_BACK_TRACE
     DWORD GetRuntimePid() { return m_pid; }
 #endif
@@ -65,8 +67,13 @@ private:
     AllocationPolicyManager m_policyManager;
     JsUtil::BaseDictionary<DWORD, PageAllocator*, HeapAllocator> m_pageAllocs;
     PreReservedVirtualAllocWrapper m_preReservedVirtualAllocator;
+#if DYNAMIC_INTERPRETER_THUNK || defined(ASMJS_PLAT)
+    CustomHeap::CodePageAllocators m_thunkPageAllocators;
+#endif
     CustomHeap::CodePageAllocators m_codePageAllocators;
     CodeGenAllocators m_codeGenAlloc;
+    // only allocate with this from foreground calls (never from CodeGen calls)
+    PageAllocator m_pageAlloc;
 
     ThreadContextDataIDL m_threadContextData;
 

+ 24 - 1
lib/Common/Memory/CustomHeap.cpp

@@ -1062,7 +1062,30 @@ void FillDebugBreak(_In_ BYTE* buffer, __in size_t byteCount, HANDLE processHand
     }
     else
     {
-        writeBuffer = HeapNewArray(BYTE, byteCount);
+        writeBuffer = HeapNewNoThrowArray(BYTE, byteCount);
+        // in oom scenario write element at a time
+        // TODO: OOP JIT, pre-allocate space so this can't happen
+        if (!writeBuffer)
+        {
+            for (size_t i = 0; i < byteCount; i+= sizeof(char16))
+            {
+                if (!WriteProcessMemory(processHandle, buffer + i, &pattern, sizeof(char16), NULL))
+                {
+                    Js::Throw::CheckAndThrowJITOperationFailed();
+                    Js::Throw::FatalInternalError();
+                }
+            }
+            if (byteCount % 2 != 0)
+            {
+                char lastByte = 0;
+                if (!WriteProcessMemory(processHandle, buffer + byteCount - 1, &lastByte, sizeof(BYTE), NULL))
+                {
+                    Js::Throw::CheckAndThrowJITOperationFailed();
+                    Js::Throw::FatalInternalError();
+                }
+            }
+            return;
+        }
     }
     wmemset((char16 *)writeBuffer, pattern, byteCount / 2);
     if (byteCount % 2)

+ 68 - 0
lib/JITClient/JITManager.cpp

@@ -373,6 +373,49 @@ JITManager::SetIsPRNGSeeded(
 
 }
 
+HRESULT
+JITManager::DecommitInterpreterBufferManager(
+    __in intptr_t scriptContextInfoAddress,
+    __in boolean asmJsThunk)
+{
+    Assert(IsOOPJITEnabled());
+
+    HRESULT hr = E_FAIL;
+    RpcTryExcept
+    {
+        hr = ClientDecommitInterpreterBufferManager(m_rpcBindingHandle, scriptContextInfoAddress, asmJsThunk);
+    }
+    RpcExcept(RpcExceptionFilter(RpcExceptionCode()))
+    {
+        hr = HRESULT_FROM_WIN32(RpcExceptionCode());
+    }
+    RpcEndExcept;
+
+    return hr;
+}
+
+HRESULT
+JITManager::NewInterpreterThunkBlock(
+    __in intptr_t scriptContextInfoAddress,
+    __in boolean asmJsThunk,
+    __out InterpreterThunkInfoIDL * thunkInfo)
+{
+    Assert(IsOOPJITEnabled());
+
+    HRESULT hr = E_FAIL;
+    RpcTryExcept
+    {
+        hr = ClientNewInterpreterThunkBlock(m_rpcBindingHandle, scriptContextInfoAddress, asmJsThunk, thunkInfo);
+    }
+    RpcExcept(RpcExceptionFilter(RpcExceptionCode()))
+    {
+        hr = HRESULT_FROM_WIN32(RpcExceptionCode());
+    }
+    RpcEndExcept;
+
+    return hr;
+}
+
 HRESULT 
 JITManager::AddModuleRecordInfo(
     /* [in] */ intptr_t scriptContextInfoAddress,
@@ -523,6 +566,31 @@ JITManager::FreeAllocation(
     return hr;
 }
 
+#if DBG
+HRESULT
+JITManager::IsInterpreterThunkAddr(
+    __in intptr_t scriptContextInfoAddress,
+    __in intptr_t address,
+    __in boolean asmjsThunk,
+    __out boolean * result)
+{
+    Assert(IsOOPJITEnabled());
+
+    HRESULT hr = E_FAIL;
+    RpcTryExcept
+    {
+        hr = ClientIsInterpreterThunkAddr(m_rpcBindingHandle, scriptContextInfoAddress, address, asmjsThunk, result);
+    }
+        RpcExcept(RpcExceptionFilter(RpcExceptionCode()))
+    {
+        hr = HRESULT_FROM_WIN32(RpcExceptionCode());
+    }
+    RpcEndExcept;
+
+    return hr;
+}
+#endif
+
 HRESULT
 JITManager::IsNativeAddr(
     __in intptr_t threadContextInfoAddress,

+ 17 - 0
lib/JITClient/JITManager.h

@@ -33,6 +33,15 @@ public:
         __in intptr_t threadContextInfoAddress,
         __in UpdatedPropertysIDL * updatedProps);
 
+    HRESULT DecommitInterpreterBufferManager(
+        __in intptr_t scriptContextInfoAddress,
+        __in boolean asmJsThunk);
+
+    HRESULT NewInterpreterThunkBlock(
+        __in intptr_t scriptContextInfoAddress,
+        __in boolean asmJsThunk,
+        __out InterpreterThunkInfoIDL * thunkInfo);
+
     HRESULT AddDOMFastPathHelper(
         __in intptr_t scriptContextInfoAddress,
         __in intptr_t funcInfoAddr,
@@ -78,6 +87,14 @@ public:
         __in intptr_t scriptContextInfoAddress,
         __out JITOutputIDL *jitData);
 
+#if DBG
+    HRESULT IsInterpreterThunkAddr(
+        __in intptr_t scriptContextInfoAddress,
+        __in intptr_t address,
+        __in boolean asmjsThunk,
+        __out boolean * result);
+#endif
+
     HRESULT Shutdown();
 
 

+ 19 - 0
lib/JITIDL/ChakraJIT.idl

@@ -68,6 +68,17 @@ interface IChakraJIT
         [in] CHAKRA_PTR threadContextInfoAddress,
         [in] CHAKRA_PTR address);
 
+    HRESULT NewInterpreterThunkBlock(
+        [in] handle_t binding,
+        [in] CHAKRA_PTR scriptContextInfoAddress,
+        [in] boolean asmJsThunk,
+        [out] InterpreterThunkInfoIDL * thunkInfo);
+
+    HRESULT DecommitInterpreterBufferManager(
+        [in] handle_t binding,
+        [in] CHAKRA_PTR scriptContextInfoAddress,
+        [in] boolean asmJsManager);
+
     HRESULT IsNativeAddr(
         [in] handle_t binding,
         [in] CHAKRA_PTR threadContextInfoAddress,
@@ -85,4 +96,12 @@ interface IChakraJIT
         [in] CodeGenWorkItemIDL * workItemData,
         [out] JITOutputIDL * jitData);
 
+#if DBG
+    HRESULT IsInterpreterThunkAddr(
+        [in] handle_t binding,
+        [in] CHAKRA_PTR scriptContextInfoAddress,
+        [in] CHAKRA_PTR address,
+        [in] boolean asmjsThunk,
+        [out] boolean * result);
+#endif
 }

+ 9 - 0
lib/JITIDL/JITTypes.h

@@ -808,3 +808,12 @@ typedef struct UpdatedPropertysIDL
     [size_is(reclaimedPropertyCount)] int * reclaimedPropertyIdArray;
     [size_is(newRecordCount)] PropertyRecordIDL ** newRecordArray;
 } UpdatedPropertysIDL;
+
+typedef struct InterpreterThunkInfoIDL
+{
+    unsigned int thunkCount;
+    X64_PAD4(0)
+    CHAKRA_PTR pdataTableStart;
+    CHAKRA_PTR epilogEndAddr;
+    CHAKRA_PTR thunkBlockAddr;
+} InterpreterThunkInfoIDL;

+ 130 - 0
lib/JITServer/JITServer.cpp

@@ -350,6 +350,107 @@ ServerCloseScriptContext(
     });
 }
 
+HRESULT
+ServerDecommitInterpreterBufferManager(
+    /* [in] */ handle_t binding,
+    /* [in] */ intptr_t scriptContextInfoAddress,
+    /* [in] */ boolean asmJsManager)
+{
+    ServerScriptContext * scriptContext = (ServerScriptContext *)DecodePointer((void*)scriptContextInfoAddress);
+
+    if (scriptContext == nullptr)
+    {
+        Assert(false);
+        return RPC_S_INVALID_ARG;
+    }
+
+    if (ServerContextManager::IsScriptContextAlive(scriptContext))
+    {
+        AutoReleaseContext<ServerScriptContext> autoScriptContext(scriptContext);
+        scriptContext->DecommitEmitBufferManager(asmJsManager != FALSE);
+    }
+    return S_OK;
+}
+
+HRESULT
+ServerNewInterpreterThunkBlock(
+    /* [in] */ handle_t binding,
+    /* [in] */ intptr_t scriptContextInfo,
+    /* [in] */ boolean asmJsThunk,
+    /* [out] */ __RPC__out InterpreterThunkInfoIDL * thunkInfo)
+{
+    ServerScriptContext * scriptContext = (ServerScriptContext *)DecodePointer((void*)scriptContextInfo);
+
+    memset(thunkInfo, 0, sizeof(InterpreterThunkInfoIDL));
+
+    if (scriptContext == nullptr)
+    {
+        Assert(false);
+        return RPC_S_INVALID_ARG;
+    }
+
+    AutoReleaseContext<ServerScriptContext> autoScriptContext(scriptContext);
+    return ServerCallWrapper(scriptContext, [&]()->HRESULT
+    {
+        AUTO_NESTED_HANDLED_EXCEPTION_TYPE(static_cast<ExceptionType>(ExceptionType_OutOfMemory | ExceptionType_StackOverflow));
+
+        const DWORD bufferSize = InterpreterThunkEmitter::BlockSize;
+        DWORD thunkCount = 0;
+
+#if PDATA_ENABLED
+        PRUNTIME_FUNCTION pdataStart;
+        intptr_t epilogEnd;
+#endif
+        ServerThreadContext * threadContext = scriptContext->GetThreadContext();
+        EmitBufferManager<> * emitBufferManager = scriptContext->GetEmitBufferManager(asmJsThunk != FALSE);
+
+        // REVIEW: OOP JIT should we clear arena at end?
+        ArenaAllocator * arena = scriptContext->GetSourceCodeArena();
+        BYTE * localBuffer = AnewArray(arena, BYTE, bufferSize);
+
+        BYTE* remoteBuffer;
+        EmitBufferAllocation * allocation = emitBufferManager->AllocateBuffer(bufferSize, &remoteBuffer);
+
+        InterpreterThunkEmitter::FillBuffer(
+            arena,
+            threadContext,
+            asmJsThunk != FALSE,
+            (intptr_t)remoteBuffer,
+            bufferSize,
+            localBuffer,
+#if PDATA_ENABLED
+            &pdataStart,
+            &epilogEnd,
+#endif
+            &thunkCount
+        );
+
+        bool success = emitBufferManager->ProtectBufferWithExecuteReadWriteForInterpreter(allocation);
+        Assert(success);
+
+        if (!WriteProcessMemory(threadContext->GetProcessHandle(), remoteBuffer, localBuffer, bufferSize, nullptr))
+        {
+            Js::Throw::JITOperationFailed(GetLastError());
+        }
+
+        success = emitBufferManager->CommitReadWriteBufferForInterpreter(allocation, remoteBuffer, bufferSize);
+        Assert(success);
+
+        // Call to set VALID flag for CFG check
+        threadContext->SetValidCallTargetForCFG(remoteBuffer);
+
+        thunkInfo->thunkBlockAddr = (intptr_t)remoteBuffer;
+        thunkInfo->thunkCount = thunkCount;
+#if PDATA_ENABLED
+        thunkInfo->pdataTableStart = (intptr_t)pdataStart;
+        thunkInfo->epilogEndAddr = epilogEnd;
+#endif
+        arena->Clear();
+
+        return S_OK;
+    });
+}
+
 HRESULT
 ServerFreeAllocation(
     /* [in] */ handle_t binding,
@@ -372,6 +473,35 @@ ServerFreeAllocation(
     });
 }
 
+#if DBG
+HRESULT
+ServerIsInterpreterThunkAddr(
+    /* [in] */ handle_t binding,
+    /* [in] */ intptr_t scriptContextInfoAddress,
+    /* [in] */ intptr_t address,
+    /* [in] */ boolean asmjsThunk,
+    /* [out] */ __RPC__out boolean * result)
+{
+    ServerScriptContext * context = (ServerScriptContext*)DecodePointer((void*)scriptContextInfoAddress);
+
+    if (context == nullptr)
+    {
+        *result = false;
+        return RPC_S_INVALID_ARG;
+    }
+    EmitBufferManager<> * manager = context->GetEmitBufferManager(asmjsThunk != FALSE);
+    if (manager == nullptr)
+    {
+        *result = false;
+        return S_OK;
+    }
+
+    *result = manager->IsInHeap((void*)address);
+
+    return S_OK;
+}
+#endif
+
 HRESULT
 ServerIsNativeAddr(
     /* [in] */ handle_t binding,

+ 2 - 1
lib/Runtime/Base/FunctionBody.cpp

@@ -851,7 +851,7 @@ namespace Js
         entryPointInfo->entryPointIndex = this->entryPoints->Add(recycler->CreateWeakReferenceHandle(entryPointInfo));
     }
 
-
+#if DBG
     BOOL FunctionBody::IsInterpreterThunk() const
     {
         bool isInterpreterThunk = this->originalEntryPoint == DefaultEntryThunk;
@@ -869,6 +869,7 @@ namespace Js
         return FALSE;
 #endif
     }
+#endif
 
     FunctionEntryPointInfo * FunctionBody::TryGetEntryPointInfo(int index) const
     {

+ 2 - 0
lib/Runtime/Base/FunctionBody.h

@@ -2628,8 +2628,10 @@ namespace Js
         void AddEntryPointToEntryPointList(FunctionEntryPointInfo* entryPoint);
 
         // Kind of entry point for original entry point
+#if DBG
         BOOL IsInterpreterThunk() const;
         BOOL IsDynamicInterpreterThunk() const;
+#endif
         BOOL IsNativeOriginalEntryPoint() const;
         bool IsSimpleJitOriginalEntryPoint() const;
 

+ 4 - 2
lib/Runtime/Base/ScriptContext.cpp

@@ -1209,11 +1209,11 @@ if (!sourceList)
         }
 
 #if DYNAMIC_INTERPRETER_THUNK
-        interpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators());
+        interpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, this, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators());
 #endif
 
 #ifdef ASMJS_PLAT
-        asmJsInterpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators(),
+        asmJsInterpreterThunkEmitter = HeapNew(InterpreterThunkEmitter, this, SourceCodeAllocator(), this->GetThreadContext()->GetThunkPageAllocators(),
             true);
 #endif
 
@@ -4848,10 +4848,12 @@ void ScriptContext::RegisterPrototypeChainEnsuredToHaveOnlyWritableDataPropertie
         return (JavascriptMethod)this->interpreterThunkEmitter->GetNextThunk(ppDynamicInterpreterThunk);
     }
 
+#if DBG
     BOOL ScriptContext::IsDynamicInterpreterThunk(JavascriptMethod address)
     {
         return this->interpreterThunkEmitter->IsInHeap((void*)address);
     }
+#endif
 
     void ScriptContext::ReleaseDynamicInterpreterThunk(BYTE* address, bool addtoFreeList)
     {

+ 4 - 2
lib/Runtime/Base/ScriptContext.h

@@ -910,10 +910,10 @@ private:
         void SetDirectHostTypeId(TypeId typeId) {directHostTypeId = typeId; }
         TypeId GetDirectHostTypeId() const { return directHostTypeId; }
 
-        intptr_t GetRemoteScriptAddr() 
+        intptr_t GetRemoteScriptAddr(bool allowInitialize = true) 
         {
 #if ENABLE_OOP_NATIVE_CODEGEN
-            if (!m_remoteScriptContextAddr)
+            if (!m_remoteScriptContextAddr && allowInitialize)
             {
                 InitializeRemoteScriptContext();
             }
@@ -1656,7 +1656,9 @@ private:
 #if DYNAMIC_INTERPRETER_THUNK
         JavascriptMethod GetNextDynamicAsmJsInterpreterThunk(PVOID* ppDynamicInterpreterThunk);
         JavascriptMethod GetNextDynamicInterpreterThunk(PVOID* ppDynamicInterpreterThunk);
+#if DBG
         BOOL IsDynamicInterpreterThunk(JavascriptMethod address);
+#endif
         void ReleaseDynamicInterpreterThunk(BYTE* address, bool addtoFreeList);
         void ReleaseDynamicAsmJsInterpreterThunk(BYTE* address, bool addtoFreeList);
 #endif