|  | /* | 
|  | * Copyright (C) 2021 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 "WasmStreamingCompiler.h" | 
|  |  | 
|  | #include "JSBigInt.h" | 
|  | #include "JSWebAssembly.h" | 
|  | #include "JSWebAssemblyCompileError.h" | 
|  | #include "JSWebAssemblyHelpers.h" | 
|  | #include "JSWebAssemblyInstance.h" | 
|  | #include "JSWebAssemblyModule.h" | 
|  | #include "StrongInlines.h" | 
|  | #include "WasmLLIntPlan.h" | 
|  | #include "WasmStreamingPlan.h" | 
|  | #include "WasmWorklist.h" | 
|  |  | 
|  | #if ENABLE(WEBASSEMBLY) | 
|  |  | 
|  | namespace JSC { namespace Wasm { | 
|  |  | 
|  | StreamingCompiler::StreamingCompiler(VM& vm, CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject) | 
|  | : m_vm(vm) | 
|  | , m_compilerMode(compilerMode) | 
|  | , m_info(Wasm::ModuleInformation::create()) | 
|  | , m_parser(m_info.get(), *this) | 
|  | { | 
|  | Vector<Strong<JSCell>> dependencies; | 
|  | dependencies.append(Strong<JSCell>(vm, globalObject)); | 
|  | if (importObject) | 
|  | dependencies.append(Strong<JSCell>(vm, importObject)); | 
|  | m_ticket = vm.deferredWorkTimer->addPendingWork(vm, promise, WTFMove(dependencies)); | 
|  | ASSERT(vm.deferredWorkTimer->hasPendingWork(m_ticket)); | 
|  | ASSERT(vm.deferredWorkTimer->hasDependancyInPendingWork(m_ticket, globalObject)); | 
|  | ASSERT(!importObject || vm.deferredWorkTimer->hasDependancyInPendingWork(m_ticket, importObject)); | 
|  | } | 
|  |  | 
|  | StreamingCompiler::~StreamingCompiler() | 
|  | { | 
|  | if (m_ticket) { | 
|  | auto ticket = std::exchange(m_ticket, nullptr); | 
|  | m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [](DeferredWorkTimer::Ticket) mutable { }); | 
|  | } | 
|  | } | 
|  |  | 
|  | Ref<StreamingCompiler> StreamingCompiler::create(VM& vm, CompilerMode compilerMode, JSGlobalObject* globalObject, JSPromise* promise, JSObject* importObject) | 
|  | { | 
|  | return adoptRef(*new StreamingCompiler(vm, compilerMode, globalObject, promise, importObject)); | 
|  | } | 
|  |  | 
|  | bool StreamingCompiler::didReceiveFunctionData(unsigned functionIndex, const Wasm::FunctionData&) | 
|  | { | 
|  | if (!m_plan) { | 
|  | m_plan = adoptRef(*new LLIntPlan(&m_vm.wasmContext, m_info.copyRef(), m_compilerMode, Plan::dontFinalize())); | 
|  | // Plan already failed in preparation. We do not start threaded compilation. | 
|  | // Keep Plan failed, and "finalize" will reject promise with that failure. | 
|  | if (!m_plan->failed()) { | 
|  | m_remainingCompilationRequests = m_info->functions.size(); | 
|  | m_threadedCompilationStarted = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (m_threadedCompilationStarted) { | 
|  | Ref<Plan> plan = adoptRef(*new StreamingPlan(&m_vm.wasmContext, m_info.copyRef(), *m_plan, functionIndex, createSharedTask<Plan::CallbackType>([compiler = Ref { *this }](Plan& plan) { | 
|  | compiler->didCompileFunction(static_cast<StreamingPlan&>(plan)); | 
|  | }))); | 
|  | ensureWorklist().enqueue(WTFMove(plan)); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::didCompileFunction(StreamingPlan& plan) | 
|  | { | 
|  | Locker locker { m_lock }; | 
|  | ASSERT(m_threadedCompilationStarted); | 
|  | if (plan.failed()) | 
|  | m_plan->didFailInStreaming(plan.errorMessage()); | 
|  | m_remainingCompilationRequests--; | 
|  | if (!m_remainingCompilationRequests) | 
|  | m_plan->didCompileFunctionInStreaming(); | 
|  | completeIfNecessary(); | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::didFinishParsing() | 
|  | { | 
|  | if (!m_plan) { | 
|  | // Reaching here means that this WebAssembly module has no functions. | 
|  | ASSERT(!m_info->functions.size()); | 
|  | ASSERT(!m_remainingCompilationRequests); | 
|  | m_plan = adoptRef(*new LLIntPlan(&m_vm.wasmContext, m_info.copyRef(), m_compilerMode, Plan::dontFinalize())); | 
|  | // If plan is already failed in preparation, we will reject promise with plan's failure soon in finalize. | 
|  | } | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::completeIfNecessary() | 
|  | { | 
|  | if (m_eagerFailed) | 
|  | return; | 
|  |  | 
|  | if (!m_remainingCompilationRequests && m_finalized) { | 
|  | m_plan->completeInStreaming(); | 
|  | didComplete(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::didComplete() | 
|  | { | 
|  |  | 
|  | auto makeValidationResult = [](JSC::Wasm::LLIntPlan& plan) -> Module::ValidationResult { | 
|  | ASSERT(!plan.hasWork()); | 
|  | if (plan.failed()) | 
|  | return Unexpected<String>(plan.errorMessage()); | 
|  | return JSC::Wasm::Module::ValidationResult(Module::create(plan)); | 
|  | }; | 
|  |  | 
|  | auto result = makeValidationResult(*m_plan); | 
|  | auto ticket = std::exchange(m_ticket, nullptr); | 
|  | switch (m_compilerMode) { | 
|  | case CompilerMode::Validation: { | 
|  | m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [result = WTFMove(result)](DeferredWorkTimer::Ticket ticket) mutable { | 
|  | JSPromise* promise = jsCast<JSPromise*>(ticket->target()); | 
|  | JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(ticket->dependencies[0].get()); | 
|  | VM& vm = globalObject->vm(); | 
|  | auto scope = DECLARE_THROW_SCOPE(vm); | 
|  |  | 
|  | JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, globalObject, globalObject->webAssemblyModuleStructure(), WTFMove(result)); | 
|  | if (UNLIKELY(scope.exception())) { | 
|  | promise->rejectWithCaughtException(globalObject, scope); | 
|  | return; | 
|  | } | 
|  |  | 
|  | scope.release(); | 
|  | promise->resolve(globalObject, module); | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | case CompilerMode::FullCompile: { | 
|  | m_vm.deferredWorkTimer->scheduleWorkSoon(ticket, [result = WTFMove(result)](DeferredWorkTimer::Ticket ticket) mutable { | 
|  | JSPromise* promise = jsCast<JSPromise*>(ticket->target()); | 
|  | JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(ticket->dependencies[0].get()); | 
|  | JSObject* importObject = jsCast<JSObject*>(ticket->dependencies[1].get()); | 
|  | VM& vm = globalObject->vm(); | 
|  | auto scope = DECLARE_THROW_SCOPE(vm); | 
|  |  | 
|  | JSWebAssemblyModule* module = JSWebAssemblyModule::createStub(vm, globalObject, globalObject->webAssemblyModuleStructure(), WTFMove(result)); | 
|  | if (UNLIKELY(scope.exception())) { | 
|  | promise->rejectWithCaughtException(globalObject, scope); | 
|  | return; | 
|  | } | 
|  |  | 
|  | JSWebAssembly::instantiateForStreaming(vm, globalObject, promise, module, importObject); | 
|  | if (UNLIKELY(scope.exception())) { | 
|  | promise->rejectWithCaughtException(globalObject, scope); | 
|  | return; | 
|  | } | 
|  | }); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::finalize(JSGlobalObject* globalObject) | 
|  | { | 
|  | auto state = m_parser.finalize(); | 
|  | if (state != StreamingParser::State::Finished) { | 
|  | fail(globalObject, createJSWebAssemblyCompileError(globalObject, globalObject->vm(), m_parser.errorMessage())); | 
|  | return; | 
|  | } | 
|  | { | 
|  | Locker locker { m_lock }; | 
|  | m_finalized = true; | 
|  | completeIfNecessary(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::fail(JSGlobalObject* globalObject, JSValue error) | 
|  | { | 
|  | { | 
|  | Locker locker { m_lock }; | 
|  | ASSERT(!m_finalized); | 
|  | if (m_eagerFailed) | 
|  | return; | 
|  | m_eagerFailed = true; | 
|  | } | 
|  | auto ticket = std::exchange(m_ticket, nullptr); | 
|  | JSPromise* promise = jsCast<JSPromise*>(ticket->target()); | 
|  | // The pending work TicketData was keeping the promise alive. We need to | 
|  | // make sure it is reachable from the stack before we remove it from the | 
|  | // pending work list. Note: m_ticket stores it as a PackedPtr, which is not | 
|  | // scannable by the GC. | 
|  | WTF::compilerFence(); | 
|  | m_vm.deferredWorkTimer->cancelPendingWork(ticket); | 
|  | promise->reject(globalObject, error); | 
|  | } | 
|  |  | 
|  | void StreamingCompiler::cancel() | 
|  | { | 
|  | { | 
|  | Locker locker { m_lock }; | 
|  | ASSERT(!m_finalized); | 
|  | if (m_eagerFailed) | 
|  | return; | 
|  | m_eagerFailed = true; | 
|  | } | 
|  | auto ticket = std::exchange(m_ticket, nullptr); | 
|  | m_vm.deferredWorkTimer->cancelPendingWork(ticket); | 
|  | } | 
|  |  | 
|  |  | 
|  | } } // namespace JSC::Wasm | 
|  |  | 
|  | #endif // ENABLE(WEBASSEMBLY) |