/* * 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 "VMInlines.h" namespace JSC { class JITWorklist::Plan : public ThreadSafeRefCounted { public: Plan(CodeBlock* codeBlock, BytecodeIndex loopOSREntryBytecodeIndex) : m_codeBlock(codeBlock) , m_jit(codeBlock->vm(), codeBlock, loopOSREntryBytecodeIndex) { 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")); dataLogLnIf(Options::verboseOSR(), " JIT compilation failed."); m_codeBlock->dontJITAnytimeSoon(); m_codeBlock->m_didFailJITCompilation = true; return; case CompilationSuccessful: dataLogLnIf(Options::verboseOSR(), " JIT compilation successful."); 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, BytecodeIndex loopOSREntryBytecodeIndex) { Plan plan(codeBlock, loopOSREntryBytecodeIndex); plan.compileInThread(); plan.finalize(); } private: CodeBlock* m_codeBlock; JIT m_jit; Lock m_lock; bool m_isFinishedCompiling { false }; }; class JITWorklist::Thread final : 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 final { #if OS(LINUX) return "JITWorker"; #else return "JIT Worklist Helper Thread"; #endif } private: PollResult poll(const AbstractLocker&) final { 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() final { RELEASE_ASSERT(!m_myPlans.isEmpty()); for (RefPtr& 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; } JITWorklist& m_worklist; Plans m_myPlans; }; JITWorklist::JITWorklist() : m_lock(Box::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, 32> myPlans; { LockHolder locker(*m_lock); for (;;) { bool didFindUnfinishedPlan = false; m_plans.removeAllMatching( [&] (RefPtr& 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) { if (&plan->vm() != &vm) return false; if (!plan->isFinishedCompiling()) return false; myPlans.append(WTFMove(plan)); return true; }); } finalizePlans(myPlans); } void JITWorklist::compileLater(CodeBlock* codeBlock, BytecodeIndex loopOSREntryBytecodeIndex) { DeferGC deferGC(codeBlock->vm().heap); RELEASE_ASSERT(codeBlock->jitType() == JITType::InterpreterThunk); if (codeBlock->m_didFailJITCompilation) { codeBlock->dontJITAnytimeSoon(); return; } if (!Options::useConcurrentJIT()) { Plan::compileNow(codeBlock, loopOSREntryBytecodeIndex); return; } codeBlock->jitSoon(); { LockHolder locker(*m_lock); if (m_planned.contains(codeBlock)) return; if (m_numAvailableThreads) { m_planned.add(codeBlock); RefPtr plan = adoptRef(new Plan(codeBlock, loopOSREntryBytecodeIndex)); 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, loopOSREntryBytecodeIndex); } void JITWorklist::compileNow(CodeBlock* codeBlock, BytecodeIndex loopOSREntryBytecodeIndex) { 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, loopOSREntryBytecodeIndex); codeBlock->ownerExecutable()->installCode(codeBlock); } void JITWorklist::finalizePlans(Plans& myPlans) { for (RefPtr& 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)