blob: 8ef1709d96b6832a311a60ae6c0554e0a50f3496 [file] [log] [blame]
/*
* Copyright (C) 2019-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 "WasmLLIntPlan.h"
#if ENABLE(WEBASSEMBLY)
#include "BytecodeDumper.h"
#include "CCallHelpers.h"
#include "CalleeBits.h"
#include "JITCompilation.h"
#include "JITOpaqueByproducts.h"
#include "JSToWasm.h"
#include "LLIntData.h"
#include "LLIntThunks.h"
#include "LinkBuffer.h"
#include "WasmCallee.h"
#include "WasmCallingConvention.h"
#include "WasmLLIntGenerator.h"
#include "WasmTypeDefinitionInlines.h"
#include <wtf/GraphNodeWorklist.h>
#include <wtf/text/MakeString.h>
namespace JSC { namespace Wasm {
LLIntPlan::LLIntPlan(VM& vm, Vector<uint8_t>&& source, CompilerMode compilerMode, CompletionTask&& task)
: Base(vm, WTFMove(source), compilerMode, WTFMove(task))
{
if (parseAndValidateModule(m_source.span()))
prepare();
}
LLIntPlan::LLIntPlan(VM& vm, Ref<ModuleInformation> info, const Ref<LLIntCallee>* callees, CompletionTask&& task)
: Base(vm, WTFMove(info), CompilerMode::FullCompile, WTFMove(task))
, m_callees(callees)
{
ASSERT(m_callees || !m_moduleInformation->functions.size());
m_areWasmToJSStubsCompiled = true;
prepare();
m_currentIndex = m_moduleInformation->functions.size();
}
LLIntPlan::LLIntPlan(VM& vm, Ref<ModuleInformation> info, CompilerMode compilerMode, CompletionTask&& task)
: Base(vm, WTFMove(info), compilerMode, WTFMove(task))
{
prepare();
m_currentIndex = m_moduleInformation->functions.size();
}
bool LLIntPlan::prepareImpl()
{
const auto& functions = m_moduleInformation->functions;
if (!tryReserveCapacity(m_wasmInternalFunctions, functions.size(), " WebAssembly functions"_s))
return false;
m_wasmInternalFunctions.resize(functions.size());
if (!tryReserveCapacity(m_entrypoints, functions.size(), " WebAssembly functions"_s))
return false;
m_entrypoints.resize(functions.size());
if (!m_callees) {
if (!tryReserveCapacity(m_calleesVector, functions.size(), " WebAssembly functions"_s))
return false;
m_calleesVector.resize(functions.size());
}
return true;
}
void LLIntPlan::compileFunction(uint32_t functionIndex)
{
const auto& function = m_moduleInformation->functions[functionIndex];
TypeIndex typeIndex = m_moduleInformation->internalFunctionTypeIndices[functionIndex];
const TypeDefinition& signature = TypeInformation::get(typeIndex).expand();
unsigned functionIndexSpace = m_moduleInformation->importFunctionTypeIndices.size() + functionIndex;
ASSERT_UNUSED(functionIndexSpace, m_moduleInformation->typeIndexFromFunctionIndexSpace(functionIndexSpace) == typeIndex);
m_unlinkedWasmToWasmCalls[functionIndex] = Vector<UnlinkedWasmToWasmCall>();
auto parseAndCompileResult = parseAndCompileBytecode(function.data, signature, m_moduleInformation.get(), functionIndex);
if (UNLIKELY(!parseAndCompileResult)) {
Locker locker { m_lock };
if (!m_errorMessage) {
// Multiple compiles could fail simultaneously. We arbitrarily choose the first.
fail(makeString(parseAndCompileResult.error(), ", in function at index "_s, functionIndex)); // FIXME make this an Expected.
}
m_currentIndex = m_moduleInformation->functions.size();
return;
}
if (Options::useWasmTailCalls()) {
Locker locker { m_lock };
for (auto successor : parseAndCompileResult->get()->tailCallSuccessors())
addTailCallEdge(m_moduleInformation->importFunctionCount() + parseAndCompileResult->get()->functionIndex(), successor);
if (parseAndCompileResult->get()->tailCallClobbersInstance())
m_moduleInformation->addClobberingTailCall(m_moduleInformation->importFunctionCount() + parseAndCompileResult->get()->functionIndex());
}
m_wasmInternalFunctions[functionIndex] = WTFMove(*parseAndCompileResult);
if (UNLIKELY(Options::dumpGeneratedWasmBytecodes()))
BytecodeDumper::dumpBlock(m_wasmInternalFunctions[functionIndex].get(), m_moduleInformation, WTF::dataFile());
LLIntCallee* llintCallee = nullptr;
if (!m_callees) {
auto callee = LLIntCallee::create(*m_wasmInternalFunctions[functionIndex], functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace));
ASSERT(!callee->entrypoint());
if (Options::useWasmJIT()) {
#if ENABLE(JIT)
if (m_moduleInformation->usesSIMD(functionIndex))
callee->setEntrypoint(LLInt::wasmFunctionEntryThunkSIMD().retaggedCode<WasmEntryPtrTag>());
else
callee->setEntrypoint(LLInt::wasmFunctionEntryThunk().retaggedCode<WasmEntryPtrTag>());
#endif
} else {
if (m_moduleInformation->usesSIMD(functionIndex)) {
Locker locker { m_lock };
Base::fail(makeString("JIT is disabled, but the entrypoint for "_s, functionIndex, " requires JIT"_s));
return;
}
callee->setEntrypoint(LLInt::getCodeFunctionPtr<CFunctionPtrTag>(wasm_function_prologue_trampoline));
}
llintCallee = callee.ptr();
m_calleesVector[functionIndex] = WTFMove(callee);
} else
llintCallee = m_callees[functionIndex].ptr();
// If the function is exported via module, then we ensure JSToWasm entrypoint.
if (m_compilerMode != CompilerMode::Validation) {
if (m_exportedFunctionIndices.contains(functionIndex)) {
if (!ensureEntrypoint(*llintCallee, functionIndex)) {
Locker locker { m_lock };
Base::fail(makeString("JIT is disabled, but the entrypoint for "_s, functionIndex, " requires JIT"_s));
return;
}
}
}
}
bool LLIntPlan::ensureEntrypoint(LLIntCallee&, unsigned functionIndex)
{
if (m_entrypoints[functionIndex])
return true;
if (!Options::useWasmJITLessJSEntrypoint())
return false;
TypeIndex typeIndex = m_moduleInformation->internalFunctionTypeIndices[functionIndex];
const TypeDefinition& signature = TypeInformation::get(typeIndex).expand();
CallInformation wasmFrameConvention = wasmCallingConvention().callInformationFor(signature, CallRole::Caller);
RegisterAtOffsetList savedResultRegisters = wasmFrameConvention.computeResultsOffsetList();
size_t totalFrameSize = wasmFrameConvention.headerAndArgumentStackSizeInBytes;
totalFrameSize += savedResultRegisters.sizeOfAreaInBytes();
totalFrameSize += JITLessJSEntrypointCallee::RegisterStackSpaceAligned;
totalFrameSize = WTF::roundUpToMultipleOf<stackAlignmentBytes()>(totalFrameSize);
m_entrypoints[functionIndex] = JITLessJSEntrypointCallee::create(totalFrameSize, typeIndex, m_moduleInformation->usesSIMD(functionIndex));
return true;
}
void LLIntPlan::didCompleteCompilation()
{
generateStubsIfNecessary();
unsigned functionCount = m_wasmInternalFunctions.size();
if (!m_callees && functionCount) {
m_callees = m_calleesVector.data();
if (!m_moduleInformation->clobberingTailCalls().isEmpty())
computeTransitiveTailCalls();
}
if (m_compilerMode == CompilerMode::Validation)
return;
for (uint32_t functionIndex = 0; functionIndex < m_moduleInformation->functions.size(); functionIndex++) {
if (!m_entrypoints[functionIndex]) {
const uint32_t functionIndexSpace = functionIndex + m_moduleInformation->importFunctionCount();
if (m_exportedFunctionIndices.contains(functionIndex) || m_moduleInformation->hasReferencedFunction(functionIndexSpace)) {
if (!ensureEntrypoint(m_callees[functionIndex].get(), functionIndex)) {
Base::fail(makeString("JIT is disabled, but the entrypoint for "_s, functionIndex, " requires JIT"_s));
return;
}
}
}
if (auto& callee = m_entrypoints[functionIndex]) {
if (callee->compilationMode() == CompilationMode::JITLessJSEntrypointMode)
static_cast<JITLessJSEntrypointCallee*>(callee.get())->wasmCallee = CalleeBits::encodeNativeCallee(&m_callees[functionIndex].get());
m_jsEntrypointCallees.add(functionIndex, callee);
}
}
for (auto& unlinked : m_unlinkedWasmToWasmCalls) {
for (auto& call : unlinked) {
CodePtr<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_callees[call.functionIndexSpace - m_moduleInformation->importFunctionCount()]->entrypoint();
MacroAssembler::repatchNearCall(call.callLocation, CodeLocationLabel<WasmEntryPtrTag>(executableAddress));
}
}
}
void LLIntPlan::completeInStreaming()
{
Locker locker { m_lock };
complete();
}
void LLIntPlan::didCompileFunctionInStreaming()
{
Locker locker { m_lock };
moveToState(EntryPlan::State::Compiled);
}
void LLIntPlan::didFailInStreaming(String&& message)
{
Locker locker { m_lock };
if (!m_errorMessage)
fail(WTFMove(message));
}
void LLIntPlan::work(CompilationEffort effort)
{
switch (m_state) {
case State::Prepared:
compileFunctions(effort);
break;
case State::Compiled:
break;
default:
break;
}
}
bool LLIntPlan::didReceiveFunctionData(unsigned, const FunctionData&)
{
// Validation is done inline by the parser
return true;
}
void LLIntPlan::addTailCallEdge(uint32_t callerIndex, uint32_t calleeIndex)
{
auto it = m_tailCallGraph.find(calleeIndex);
if (it == m_tailCallGraph.end())
it = m_tailCallGraph.add(calleeIndex, TailCallGraph::MappedType()).iterator;
it->value.add(callerIndex);
}
void LLIntPlan::computeTransitiveTailCalls() const
{
GraphNodeWorklist<uint32_t, HashSet<uint32_t, IntHash<uint32_t>, WTF::UnsignedWithZeroKeyHashTraits<uint32_t>>> worklist;
for (auto clobberingTailCall : m_moduleInformation->clobberingTailCalls())
worklist.push(clobberingTailCall);
while (worklist.notEmpty()) {
auto node = worklist.pop();
auto it = m_tailCallGraph.find(node);
if (it == m_tailCallGraph.end())
continue;
for (const auto &successor : it->value) {
if (worklist.saw(successor))
continue;
m_moduleInformation->addClobberingTailCall(successor);
worklist.push(successor);
}
}
}
} } // namespace JSC::Wasm
#endif // ENABLE(WEBASSEMBLY)