blob: 49d52fa70e6142d3e4e28463b11f98f4a1f6bd8d [file] [log] [blame]
/*
* 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 "WasmBBQPlan.h"
#if ENABLE(WEBASSEMBLY)
#include "B3Compilation.h"
#include "WasmAirIRGenerator.h"
#include "WasmB3IRGenerator.h"
#include "WasmBinding.h"
#include "WasmCallee.h"
#include "WasmCallingConvention.h"
#include "WasmFaultSignalHandler.h"
#include "WasmMemory.h"
#include "WasmSignatureInlines.h"
#include "WasmTierUpCount.h"
#include "WasmValidate.h"
#include <wtf/DataLog.h>
#include <wtf/Locker.h>
#include <wtf/MonotonicTime.h>
#include <wtf/StdLibExtras.h>
#include <wtf/SystemTracing.h>
#include <wtf/text/StringConcatenateNumbers.h>
namespace JSC { namespace Wasm {
namespace WasmBBQPlanInternal {
static constexpr bool verbose = false;
}
BBQPlan::BBQPlan(Context* context, Ref<ModuleInformation> info, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
: Base(context, WTFMove(info), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
, m_streamingParser(m_moduleInformation.get(), *this)
, m_state(State::Validated)
, m_asyncWork(work)
{
}
BBQPlan::BBQPlan(Context* context, Vector<uint8_t>&& source, AsyncWork work, CompletionTask&& task, CreateEmbedderWrapper&& createEmbedderWrapper, ThrowWasmException throwWasmException)
: Base(context, ModuleInformation::create(), WTFMove(task), WTFMove(createEmbedderWrapper), throwWasmException)
, m_source(WTFMove(source))
, m_streamingParser(m_moduleInformation.get(), *this)
, m_state(State::Initial)
, m_asyncWork(work)
{
}
BBQPlan::BBQPlan(Context* context, AsyncWork work, CompletionTask&& task)
: Base(context, WTFMove(task))
, m_streamingParser(m_moduleInformation.get(), *this)
, m_state(State::Initial)
, m_asyncWork(work)
{
}
const char* BBQPlan::stateString(State state)
{
switch (state) {
case State::Initial: return "Initial";
case State::Validated: return "Validated";
case State::Prepared: return "Prepared";
case State::Compiled: return "Compiled";
case State::Completed: return "Completed";
}
RELEASE_ASSERT_NOT_REACHED();
}
void BBQPlan::moveToState(State state)
{
ASSERT(state >= m_state);
dataLogLnIf(WasmBBQPlanInternal::verbose && state != m_state, "moving to state: ", stateString(state), " from state: ", stateString(m_state));
m_state = state;
}
bool BBQPlan::didReceiveFunctionData(unsigned functionIndex, const FunctionData& function)
{
dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing function starting at: ", function.start, " and ending at: ", function.end);
size_t functionLength = function.end - function.start;
ASSERT(functionLength == function.data.size());
SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
const Signature& signature = SignatureInformation::get(signatureIndex);
auto validationResult = validateFunction(function, signature, m_moduleInformation.get());
if (!validationResult) {
if (WasmBBQPlanInternal::verbose) {
for (unsigned i = 0; i < functionLength; ++i)
dataLog(RawPointer(reinterpret_cast<void*>(function.data[i])), ", ");
dataLogLn();
}
fail(holdLock(m_lock), makeString(validationResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
}
return !!validationResult;
}
bool BBQPlan::parseAndValidateModule(const uint8_t* source, size_t sourceLength)
{
if (m_state != State::Initial)
return true;
dataLogLnIf(WasmBBQPlanInternal::verbose, "starting validation");
MonotonicTime startTime;
if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
startTime = MonotonicTime::now();
m_streamingParser.addBytes(source, sourceLength);
{
auto locker = holdLock(m_lock);
if (failed())
return false;
}
if (m_streamingParser.finalize() != StreamingParser::State::Finished) {
fail(holdLock(m_lock), String(m_streamingParser.errorMessage()));
return false;
}
if (WasmBBQPlanInternal::verbose || Options::reportCompileTimes())
dataLogLn("Took ", (MonotonicTime::now() - startTime).microseconds(), " us to validate module");
moveToState(State::Validated);
if (m_asyncWork == Validation)
complete(holdLock(m_lock));
return true;
}
void BBQPlan::prepare()
{
ASSERT(m_state == State::Validated);
dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting preparation");
auto tryReserveCapacity = [this] (auto& vector, size_t size, const char* what) {
if (UNLIKELY(!vector.tryReserveCapacity(size))) {
fail(holdLock(m_lock), WTF::makeString("Failed allocating enough space for ", size, what));
return false;
}
return true;
};
const auto& functions = m_moduleInformation->functions;
if (!tryReserveCapacity(m_wasmToWasmExitStubs, m_moduleInformation->importFunctionSignatureIndices.size(), " WebAssembly to JavaScript stubs")
|| !tryReserveCapacity(m_unlinkedWasmToWasmCalls, functions.size(), " unlinked WebAssembly to WebAssembly calls")
|| !tryReserveCapacity(m_wasmInternalFunctions, functions.size(), " WebAssembly functions")
|| !tryReserveCapacity(m_compilationContexts, functions.size(), " compilation contexts")
|| !tryReserveCapacity(m_tierUpCounts, functions.size(), " tier-up counts"))
return;
m_unlinkedWasmToWasmCalls.resize(functions.size());
m_wasmInternalFunctions.resize(functions.size());
m_compilationContexts.resize(functions.size());
m_tierUpCounts.resize(functions.size());
for (unsigned importIndex = 0; importIndex < m_moduleInformation->imports.size(); ++importIndex) {
Import* import = &m_moduleInformation->imports[importIndex];
if (import->kind != ExternalKind::Function)
continue;
unsigned importFunctionIndex = m_wasmToWasmExitStubs.size();
dataLogLnIf(WasmBBQPlanInternal::verbose, "Processing import function number ", importFunctionIndex, ": ", makeString(import->module), ": ", makeString(import->field));
auto binding = wasmToWasm(importFunctionIndex);
if (UNLIKELY(!binding)) {
switch (binding.error()) {
case BindingFailure::OutOfMemory:
return fail(holdLock(m_lock), makeString("Out of executable memory at import ", String::number(importIndex)));
}
RELEASE_ASSERT_NOT_REACHED();
}
m_wasmToWasmExitStubs.uncheckedAppend(binding.value());
}
const uint32_t importFunctionCount = m_moduleInformation->importFunctionCount();
for (const auto& exp : m_moduleInformation->exports) {
if (exp.kindIndex >= importFunctionCount)
m_exportedFunctionIndices.add(exp.kindIndex - importFunctionCount);
}
for (const auto& element : m_moduleInformation->elements) {
for (const uint32_t elementIndex : element.functionIndices) {
if (elementIndex >= importFunctionCount)
m_exportedFunctionIndices.add(elementIndex - importFunctionCount);
}
}
if (m_moduleInformation->startFunctionIndexSpace && m_moduleInformation->startFunctionIndexSpace >= importFunctionCount)
m_exportedFunctionIndices.add(*m_moduleInformation->startFunctionIndexSpace - importFunctionCount);
moveToState(State::Prepared);
}
// We don't have a semaphore class... and this does kinda interesting things.
class BBQPlan::ThreadCountHolder {
public:
ThreadCountHolder(BBQPlan& plan)
: m_plan(plan)
{
LockHolder locker(m_plan.m_lock);
m_plan.m_numberOfActiveThreads++;
}
~ThreadCountHolder()
{
LockHolder locker(m_plan.m_lock);
m_plan.m_numberOfActiveThreads--;
if (!m_plan.m_numberOfActiveThreads && !m_plan.hasWork())
m_plan.complete(locker);
}
BBQPlan& m_plan;
};
void BBQPlan::compileFunctions(CompilationEffort effort)
{
ASSERT(m_state >= State::Prepared);
dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting compilation");
if (!hasWork())
return;
Optional<TraceScope> traceScope;
if (Options::useTracePoints())
traceScope.emplace(WebAssemblyCompileStart, WebAssemblyCompileEnd);
ThreadCountHolder holder(*this);
size_t bytesCompiled = 0;
const auto& functions = m_moduleInformation->functions;
while (true) {
if (effort == Partial && bytesCompiled >= Options::webAssemblyPartialCompileLimit())
return;
uint32_t functionIndex;
{
auto locker = holdLock(m_lock);
if (m_currentIndex >= functions.size()) {
if (hasWork())
moveToState(State::Compiled);
return;
}
functionIndex = m_currentIndex;
++m_currentIndex;
}
const auto& function = functions[functionIndex];
SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
const Signature& signature = SignatureInformation::get(signatureIndex);
unsigned functionIndexSpace = m_wasmToWasmExitStubs.size() + functionIndex;
ASSERT_UNUSED(functionIndexSpace, m_moduleInformation->signatureIndexFromFunctionIndexSpace(functionIndexSpace) == signatureIndex);
ASSERT(validateFunction(function, signature, m_moduleInformation.get()));
m_unlinkedWasmToWasmCalls[functionIndex] = Vector<UnlinkedWasmToWasmCall>();
if (Options::useBBQTierUpChecks())
m_tierUpCounts[functionIndex] = makeUnique<TierUpCount>();
else
m_tierUpCounts[functionIndex] = nullptr;
TierUpCount* tierUp = m_tierUpCounts[functionIndex].get();
Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileResult;
unsigned osrEntryScratchBufferSize = 0;
// FIXME: Some webpages use very large Wasm module, and it exhausts all executable memory in ARM64 devices since the size of executable memory region is only limited to 128MB.
// The long term solution should be to introduce a Wasm interpreter. But as a short term solution, we introduce heuristics to switch back to BBQ B3 at the sacrifice of start-up time,
// as BBQ Air bloats such lengthy Wasm code and will consume a large amount of executable memory.
bool forceUsingB3 = false;
if (Options::webAssemblyBBQAirModeThreshold() && m_moduleInformation->codeSectionSize >= Options::webAssemblyBBQAirModeThreshold())
forceUsingB3 = true;
if (!forceUsingB3 && Options::wasmBBQUsesAir())
parseAndCompileResult = parseAndCompileAir(m_compilationContexts[functionIndex], function, signature, m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex, tierUp, m_throwWasmException);
else
parseAndCompileResult = parseAndCompile(m_compilationContexts[functionIndex], function, signature, m_unlinkedWasmToWasmCalls[functionIndex], osrEntryScratchBufferSize, m_moduleInformation.get(), m_mode, CompilationMode::BBQMode, functionIndex, UINT32_MAX, tierUp, m_throwWasmException);
if (UNLIKELY(!parseAndCompileResult)) {
auto locker = holdLock(m_lock);
fail(locker, makeString(parseAndCompileResult.error(), ", in function at index ", String::number(functionIndex))); // FIXME make this an Expected.
m_currentIndex = functions.size();
return;
}
m_wasmInternalFunctions[functionIndex] = WTFMove(*parseAndCompileResult);
if (m_exportedFunctionIndices.contains(functionIndex) || m_moduleInformation->referencedFunctions().contains(functionIndex)) {
auto locker = holdLock(m_lock);
auto result = m_embedderToWasmInternalFunctions.add(functionIndex, m_createEmbedderWrapper(m_compilationContexts[functionIndex], signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex));
ASSERT_UNUSED(result, result.isNewEntry);
}
bytesCompiled += function.data.size();
}
}
void BBQPlan::complete(const AbstractLocker& locker)
{
ASSERT(m_state != State::Compiled || m_currentIndex >= m_moduleInformation->functions.size());
dataLogLnIf(WasmBBQPlanInternal::verbose, "Starting Completion");
if (!failed() && m_state == State::Compiled) {
for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functions.size(); functionIndex++) {
CompilationContext& context = m_compilationContexts[functionIndex];
SignatureIndex signatureIndex = m_moduleInformation->internalFunctionSignatureIndices[functionIndex];
const Signature& signature = SignatureInformation::get(signatureIndex);
const uint32_t functionIndexSpace = functionIndex + m_moduleInformation->importFunctionCount();
ASSERT(functionIndexSpace < m_moduleInformation->functionIndexSpaceSize());
{
LinkBuffer linkBuffer(*context.wasmEntrypointJIT, nullptr, JITCompilationCanFail);
if (UNLIKELY(linkBuffer.didFailToAllocate())) {
fail(locker, makeString("Out of executable memory in function at index ", String::number(functionIndex)));
return;
}
m_wasmInternalFunctions[functionIndex]->entrypoint.compilation = makeUnique<B3::Compilation>(
FINALIZE_WASM_CODE_FOR_MODE(CompilationMode::BBQMode, linkBuffer, B3CompilationPtrTag, "WebAssembly BBQ function[%i] %s name %s", functionIndex, signature.toString().ascii().data(), makeString(IndexOrName(functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace))).ascii().data()),
WTFMove(context.wasmEntrypointByproducts));
}
if (auto embedderToWasmInternalFunction = m_embedderToWasmInternalFunctions.get(functionIndex)) {
LinkBuffer linkBuffer(*context.embedderEntrypointJIT, nullptr, JITCompilationCanFail);
if (UNLIKELY(linkBuffer.didFailToAllocate())) {
fail(locker, makeString("Out of executable memory in function entrypoint at index ", String::number(functionIndex)));
return;
}
embedderToWasmInternalFunction->entrypoint.compilation = makeUnique<B3::Compilation>(
FINALIZE_WASM_CODE_FOR_MODE(CompilationMode::EmbedderEntrypointMode, linkBuffer, B3CompilationPtrTag, "Embedder->WebAssembly entrypoint[%i] %s name %s", functionIndex, signature.toString().ascii().data(), makeString(IndexOrName(functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace))).ascii().data()),
WTFMove(context.embedderEntrypointByproducts));
}
}
for (auto& unlinked : m_unlinkedWasmToWasmCalls) {
for (auto& call : unlinked) {
MacroAssemblerCodePtr<WasmEntryPtrTag> executableAddress;
if (m_moduleInformation->isImportedFunctionFromFunctionIndexSpace(call.functionIndexSpace)) {
// FIXME imports could have been linked in B3, instead of generating a patchpoint. This condition should be replaced by a RELEASE_ASSERT. https://bugs.webkit.org/show_bug.cgi?id=166462
executableAddress = m_wasmToWasmExitStubs.at(call.functionIndexSpace).code();
} else
executableAddress = m_wasmInternalFunctions.at(call.functionIndexSpace - m_moduleInformation->importFunctionCount())->entrypoint.compilation->code().retagged<WasmEntryPtrTag>();
MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(executableAddress));
}
}
}
if (!isComplete()) {
moveToState(State::Completed);
runCompletionTasks(locker);
}
}
void BBQPlan::work(CompilationEffort effort)
{
switch (m_state) {
case State::Initial:
parseAndValidateModule(m_source.data(), m_source.size());
if (!hasWork()) {
ASSERT(isComplete());
return;
}
FALLTHROUGH;
case State::Validated:
prepare();
return;
case State::Prepared:
compileFunctions(effort);
return;
default:
break;
}
return;
}
} } // namespace JSC::Wasm
#endif // ENABLE(WEBASSEMBLY)