| /* |
| * Copyright (C) 2016-2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "JITWorklist.h" |
| |
| #if ENABLE(JIT) |
| |
| #include "JIT.h" |
| #include "JSCInlines.h" |
| #include "VMInlines.h" |
| |
| namespace JSC { |
| |
| class JITWorklist::Plan : public ThreadSafeRefCounted<JITWorklist::Plan> { |
| public: |
| Plan(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset) |
| : m_codeBlock(codeBlock) |
| , m_jit(codeBlock->vm(), codeBlock, loopOSREntryBytecodeOffset) |
| { |
| m_jit.doMainThreadPreparationBeforeCompile(); |
| } |
| |
| void compileInThread() |
| { |
| m_jit.compileWithoutLinking(JITCompilationCanFail); |
| |
| LockHolder locker(m_lock); |
| m_isFinishedCompiling = true; |
| } |
| |
| void finalize() |
| { |
| CompilationResult result = m_jit.link(); |
| switch (result) { |
| case CompilationFailed: |
| CODEBLOCK_LOG_EVENT(m_codeBlock, "delayJITCompile", ("compilation failed")); |
| if (Options::verboseOSR()) |
| dataLogF(" JIT compilation failed.\n"); |
| m_codeBlock->dontJITAnytimeSoon(); |
| m_codeBlock->m_didFailJITCompilation = true; |
| return; |
| case CompilationSuccessful: |
| if (Options::verboseOSR()) |
| dataLogF(" JIT compilation successful.\n"); |
| m_codeBlock->ownerExecutable()->installCode(m_codeBlock); |
| m_codeBlock->jitSoon(); |
| return; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| } |
| } |
| |
| CodeBlock* codeBlock() { return m_codeBlock; } |
| VM& vm() { return m_codeBlock->vm(); } |
| |
| bool isFinishedCompiling() |
| { |
| LockHolder locker(m_lock); |
| return m_isFinishedCompiling; |
| } |
| |
| static void compileNow(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset) |
| { |
| Plan plan(codeBlock, loopOSREntryBytecodeOffset); |
| plan.compileInThread(); |
| plan.finalize(); |
| } |
| |
| private: |
| CodeBlock* m_codeBlock; |
| JIT m_jit; |
| Lock m_lock; |
| bool m_isFinishedCompiling { false }; |
| }; |
| |
| class JITWorklist::Thread : public AutomaticThread { |
| public: |
| Thread(const AbstractLocker& locker, JITWorklist& worklist) |
| : AutomaticThread(locker, worklist.m_lock, worklist.m_condition.copyRef()) |
| , m_worklist(worklist) |
| { |
| m_worklist.m_numAvailableThreads++; |
| } |
| |
| const char* name() const override |
| { |
| #if OS(LINUX) |
| return "JITWorker"; |
| #else |
| return "JIT Worklist Helper Thread"; |
| #endif |
| } |
| |
| protected: |
| PollResult poll(const AbstractLocker&) override |
| { |
| RELEASE_ASSERT(m_worklist.m_numAvailableThreads); |
| |
| if (m_worklist.m_queue.isEmpty()) |
| return PollResult::Wait; |
| |
| m_myPlans = WTFMove(m_worklist.m_queue); |
| m_worklist.m_numAvailableThreads--; |
| return PollResult::Work; |
| } |
| |
| WorkResult work() override |
| { |
| RELEASE_ASSERT(!m_myPlans.isEmpty()); |
| |
| for (RefPtr<Plan>& plan : m_myPlans) { |
| plan->compileInThread(); |
| plan = nullptr; |
| |
| // Make sure that the main thread realizes that we just compiled something. Notifying |
| // a condition is basically free if nobody is waiting. |
| LockHolder locker(*m_worklist.m_lock); |
| m_worklist.m_condition->notifyAll(locker); |
| } |
| |
| m_myPlans.clear(); |
| |
| LockHolder locker(*m_worklist.m_lock); |
| m_worklist.m_numAvailableThreads++; |
| return WorkResult::Continue; |
| } |
| |
| private: |
| JITWorklist& m_worklist; |
| Plans m_myPlans; |
| }; |
| |
| JITWorklist::JITWorklist() |
| : m_lock(Box<Lock>::create()) |
| , m_condition(AutomaticThreadCondition::create()) |
| { |
| LockHolder locker(*m_lock); |
| m_thread = new Thread(locker, *this); |
| } |
| |
| JITWorklist::~JITWorklist() |
| { |
| UNREACHABLE_FOR_PLATFORM(); |
| } |
| |
| bool JITWorklist::completeAllForVM(VM& vm) |
| { |
| bool result = false; |
| DeferGC deferGC(vm.heap); |
| for (;;) { |
| Vector<RefPtr<Plan>, 32> myPlans; |
| { |
| LockHolder locker(*m_lock); |
| for (;;) { |
| bool didFindUnfinishedPlan = false; |
| m_plans.removeAllMatching( |
| [&] (RefPtr<Plan>& plan) { |
| if (&plan->vm() != &vm) |
| return false; |
| if (!plan->isFinishedCompiling()) { |
| didFindUnfinishedPlan = true; |
| return false; |
| } |
| myPlans.append(WTFMove(plan)); |
| return true; |
| }); |
| |
| // If we found plans then we should finalize them now. |
| if (!myPlans.isEmpty()) |
| break; |
| |
| // If we don't find plans, then we're either done or we need to wait, depending on |
| // whether we found some unfinished plans. |
| if (!didFindUnfinishedPlan) |
| return result; |
| |
| m_condition->wait(*m_lock); |
| } |
| } |
| |
| RELEASE_ASSERT(!myPlans.isEmpty()); |
| result = true; |
| finalizePlans(myPlans); |
| } |
| } |
| |
| void JITWorklist::poll(VM& vm) |
| { |
| DeferGC deferGC(vm.heap); |
| Plans myPlans; |
| { |
| LockHolder locker(*m_lock); |
| m_plans.removeAllMatching( |
| [&] (RefPtr<Plan>& plan) { |
| if (&plan->vm() != &vm) |
| return false; |
| if (!plan->isFinishedCompiling()) |
| return false; |
| myPlans.append(WTFMove(plan)); |
| return true; |
| }); |
| } |
| |
| finalizePlans(myPlans); |
| } |
| |
| void JITWorklist::compileLater(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset) |
| { |
| DeferGC deferGC(codeBlock->vm().heap); |
| RELEASE_ASSERT(codeBlock->jitType() == JITType::InterpreterThunk); |
| |
| if (codeBlock->m_didFailJITCompilation) { |
| codeBlock->dontJITAnytimeSoon(); |
| return; |
| } |
| |
| if (!Options::useConcurrentJIT()) { |
| Plan::compileNow(codeBlock, loopOSREntryBytecodeOffset); |
| return; |
| } |
| |
| codeBlock->jitSoon(); |
| |
| { |
| LockHolder locker(*m_lock); |
| |
| if (m_planned.contains(codeBlock)) |
| return; |
| |
| if (m_numAvailableThreads) { |
| m_planned.add(codeBlock); |
| RefPtr<Plan> plan = adoptRef(new Plan(codeBlock, loopOSREntryBytecodeOffset)); |
| m_plans.append(plan); |
| m_queue.append(plan); |
| m_condition->notifyAll(locker); |
| return; |
| } |
| } |
| |
| // Compiling on the main thread if the helper thread isn't available is a defense against this |
| // pathology: |
| // |
| // 1) Do something that is allowed to take a while, like load a giant piece of initialization |
| // code. This plans the compile of the init code, but doesn't finish it. It will take a |
| // while. |
| // |
| // 2) Do something that is supposed to be quick. Now all baseline compiles, and so all DFG and |
| // FTL compiles, of everything is blocked on the long-running baseline compile of that |
| // initialization code. |
| // |
| // The single-threaded concurrent JIT has this tendency to convoy everything while at the same |
| // time postponing when it happens, which means that the convoy delays are less predictable. |
| // This works around the issue. If the concurrent JIT thread is convoyed, we revert to main |
| // thread compiles. This is probably not as good as if we had multiple JIT threads. Maybe we |
| // can do that someday. |
| Plan::compileNow(codeBlock, loopOSREntryBytecodeOffset); |
| } |
| |
| void JITWorklist::compileNow(CodeBlock* codeBlock, unsigned loopOSREntryBytecodeOffset) |
| { |
| VM& vm = codeBlock->vm(); |
| DeferGC deferGC(vm.heap); |
| if (codeBlock->jitType() != JITType::InterpreterThunk) |
| return; |
| |
| bool isPlanned; |
| { |
| LockHolder locker(*m_lock); |
| isPlanned = m_planned.contains(codeBlock); |
| } |
| |
| if (isPlanned) { |
| RELEASE_ASSERT(Options::useConcurrentJIT()); |
| // This is expensive, but probably good enough. |
| completeAllForVM(vm); |
| } |
| |
| // Now it might be compiled! |
| if (codeBlock->jitType() != JITType::InterpreterThunk) |
| return; |
| |
| // We do this in case we had previously attempted, and then failed, to compile with the |
| // baseline JIT. |
| codeBlock->resetJITData(); |
| |
| // OK, just compile it. |
| JIT::compile(vm, codeBlock, JITCompilationMustSucceed, loopOSREntryBytecodeOffset); |
| codeBlock->ownerExecutable()->installCode(codeBlock); |
| } |
| |
| void JITWorklist::finalizePlans(Plans& myPlans) |
| { |
| for (RefPtr<Plan>& plan : myPlans) { |
| plan->finalize(); |
| |
| LockHolder locker(*m_lock); |
| m_planned.remove(plan->codeBlock()); |
| } |
| } |
| |
| static JITWorklist* theGlobalJITWorklist { nullptr }; |
| |
| JITWorklist* JITWorklist::existingGlobalWorklistOrNull() |
| { |
| return theGlobalJITWorklist; |
| } |
| |
| JITWorklist& JITWorklist::ensureGlobalWorklist() |
| { |
| static std::once_flag once; |
| std::call_once( |
| once, |
| [] { |
| auto* worklist = new JITWorklist(); |
| WTF::storeStoreFence(); |
| theGlobalJITWorklist = worklist; |
| }); |
| return *theGlobalJITWorklist; |
| } |
| |
| } // namespace JSC |
| |
| #endif // ENABLE(JIT) |
| |