[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/WebKit/WebKit/main/Source/JavaScriptCore/jit/JITThunks.cpp [Back]  [Original]

/*
 * Copyright (C) 2012-2023 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 "JITThunks.h"

#if ENABLE(JIT)

#include "CommonSlowPaths.h"
#include "JIT.h"
#include "JITCode.h"
#include "JSCJSValueInlines.h"
#include "LLIntThunks.h"
#include "SlowPathCall.h"
#include "ThunkGenerators.h"
#include "VM.h"
#include "YarrJIT.h"
#include 
#include 
#include 
#include 

WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN

namespace JSC {

WTF_MAKE_TZONE_ALLOCATED_IMPL(JITThunks);

JITThunks::JITThunks() = default;

JITThunks::~JITThunks() = default;

using SharedCommonThunks = std::array;

static const SharedCommonThunks& sharedCommonThunks()
{
    static LazyNeverDestroyed thunks;
    static std::once_flag onceKey;
    std::call_once(onceKey, [] {
        thunks.construct();
        unsigned index = 0;
#define JSC_DEFINE_SHARED_JIT_THUNK(name, func) thunks.get()[index++] = func();
JSC_FOR_EACH_VM_INDEPENDENT_COMMON_THUNK(JSC_DEFINE_SHARED_JIT_THUNK)
#undef JSC_DEFINE_SHARED_JIT_THUNK
    });
    return thunks.get();
}

static ThunkGenerator generatorForLazyCommonThunk(CommonJITThunkID thunkID)
{
    switch (thunkID) {
#define JSC_CASE_COMMON_JIT_THUNK(name, func) \
    case CommonJITThunkID::name: return func;
JSC_FOR_EACH_VM_DEPENDENT_LAZY_COMMON_THUNK(JSC_CASE_COMMON_JIT_THUNK)
#undef JSC_CASE_COMMON_JIT_THUNK
    default:
        break;
    }
    RELEASE_ASSERT_NOT_REACHED();
}

void JITThunks::initialize(VM& vm)
{
    ASSERT(!isCompilationThread());
    sharedCommonThunks();
#define JSC_DEFINE_COMMON_JIT_THUNK(name, func) \
    m_eagerCommonThunks[static_cast(CommonJITThunkID::name) - numberOfVMIndependentCommonThunkIDs] = func(vm);
JSC_FOR_EACH_VM_DEPENDENT_EAGER_COMMON_THUNK(JSC_DEFINE_COMMON_JIT_THUNK)
#undef JSC_DEFINE_COMMON_JIT_THUNK
}

static inline NativeExecutable& NODELETE getMayBeDyingNativeExecutable(const Weak& weak)
{
    // This never gets Deleted / Empty slots.
    WeakImpl* impl = weak.unsafeImpl();
    ASSERT(impl);
    // We have a callback removing entry when finalizing. This means that we never hold Deallocated entry in HashSet.
    ASSERT(impl->state() != WeakImpl::State::Deallocated);
    // Never use jsCast here. This is possible that this value is "Dead" but not "Finalized" yet. In this case,
    // we can still access to non-JS data, as we are doing in a finalize callback.
    auto* executable = static_cast(impl->jsValue().asCell());
    ASSERT(executable);
    return *executable;
}

inline unsigned JITThunks::WeakNativeExecutableHash::hash(const NativeExecutable* executable)
{
    return hash(executable->function(), executable->constructor(), executable->implementationVisibility(), executable->length(), executable->name());
}

inline unsigned JITThunks::WeakNativeExecutableHash::hash(const Weak& key)
{
    return hash(&getMayBeDyingNativeExecutable(key));
}

inline bool JITThunks::WeakNativeExecutableHash::equal(const NativeExecutable& a, const NativeExecutable& b)
{
    if (&a == &b)
        return true;
    return a.function() == b.function() && a.constructor() == b.constructor() && a.implementationVisibility() == b.implementationVisibility() && a.length() == b.length() && a.name() == b.name();
}

inline bool JITThunks::WeakNativeExecutableHash::equal(const Weak& a, const Weak& b)
{
    return equal(getMayBeDyingNativeExecutable(a), getMayBeDyingNativeExecutable(b));
}

inline bool JITThunks::WeakNativeExecutableHash::equal(const Weak& a, const NativeExecutable* bExecutable)
{
    return equal(getMayBeDyingNativeExecutable(a), *bExecutable);
}

inline bool JITThunks::WeakNativeExecutableHash::equal(const Weak& a, const HostFunctionKey& b)
{
    auto& aExecutable = getMayBeDyingNativeExecutable(a);
    return aExecutable.function() == std::get(b) && aExecutable.constructor() == std::get(b) && aExecutable.implementationVisibility() == std::get(b) && aExecutable.length() == std::get(b) && aExecutable.name() == std::get(b);
}

CodePtr JITThunks::ctiNativeCall(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, CommonJITThunkID::NativeCall).code();
}

CodePtr JITThunks::ctiNativeCallWithDebuggerHook(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, nativeCallWithDebuggerHookGenerator).code();
}

CodePtr JITThunks::ctiNativeConstruct(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, CommonJITThunkID::NativeConstruct).code();
}

CodePtr JITThunks::ctiNativeConstructWithDebuggerHook(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, nativeConstructWithDebuggerHookGenerator).code();
}

CodePtr JITThunks::ctiNativeTailCall(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, CommonJITThunkID::NativeTailCall).code();
}

CodePtr JITThunks::ctiNativeTailCallWithoutSavedTags(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, CommonJITThunkID::NativeTailCallWithoutSavedTags).code();
}

CodePtr JITThunks::ctiInternalFunctionCall(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, CommonJITThunkID::InternalFunctionCall).code();
}

CodePtr JITThunks::ctiInternalFunctionConstruct(VM& vm)
{
    ASSERT(Options::useJIT());
    return ctiStub(vm, CommonJITThunkID::InternalFunctionConstruct).code();
}

template 
MacroAssemblerCodeRef JITThunks::ctiStubImpl(ThunkGenerator key, GenerateThunk generateThunk)
{
    Locker locker { m_lock };

    auto handleEntry = [&] (Entry& entry) {
        if (entry.needsCrossModifyingCodeFence && !isCompilationThread()) {
            // The main thread will issue a crossModifyingCodeFence before running
            // any code the compiler thread generates, including any thunks that they
            // generate. However, the main thread may grab the thunk a compiler thread
            // generated before we've issued that crossModifyingCodeFence. Hence, we
            // conservatively say that when the main thread grabs a thunk generated
            // from a compiler thread for the first time, it issues a crossModifyingCodeFence.
            WTF::crossModifyingCodeFence();
            entry.needsCrossModifyingCodeFence = false;
        }

        return MacroAssemblerCodeRef(*entry.handle);
    };

    {
        auto iter = m_ctiStubMap.find(key);
        if (iter != m_ctiStubMap.end())
            return handleEntry(iter->value);
    }

    // We do two lookups on first addition to the hash table because generateThunk may add to it.
    MacroAssemblerCodeRef codeRef = generateThunk();

    bool needsCrossModifyingCodeFence = isCompilationThread();
    auto addResult = m_ctiStubMap.add(key, Entry { PackedRefPtr(codeRef.executableMemory()), needsCrossModifyingCodeFence });
    RELEASE_ASSERT(addResult.isNewEntry); // Thunks aren't recursive, so anything we generated transitively shouldn't have generated 'key'.
    return handleEntry(addResult.iterator->value);
}

MacroAssemblerCodeRef JITThunks::ctiStub(VM& vm, ThunkGenerator generator)
{
    return ctiStubImpl(generator, [&] {
        return generator(vm);
    });
}

MacroAssemblerCodeRef JITThunks::ctiStub(VM& vm, CommonJITThunkID thunkID)
{
    unsigned index = static_cast(thunkID);
    if (index < numberOfVMIndependentCommonThunkIDs)
        return sharedCommonThunks()[index];
    unsigned vmDependentIndex = index - numberOfVMIndependentCommonThunkIDs;
    if (vmDependentIndex < numberOfVMDependentEagerCommonThunkIDs) {
        auto result = m_eagerCommonThunks[vmDependentIndex];
        ASSERT(result);
        return result;
    }
    return lazyCommonThunk(vm, thunkID);
}

MacroAssemblerCodeRef JITThunks::lazyCommonThunk(VM& vm, CommonJITThunkID thunkID)
{
    auto& thunk = m_lazyCommonThunks[static_cast(thunkID) - numberOfVMIndependentCommonThunkIDs - numberOfVMDependentEagerCommonThunkIDs];

    auto state = thunk.state.load(std::memory_order_acquire);
    if (state == LazyThunkState::NotGenerated) {
        Locker locker { thunk.lock };
        state = thunk.state.loadRelaxed();
        if (state == LazyThunkState::NotGenerated) {
            thunk.codeRef = generatorForLazyCommonThunk(thunkID)(vm);
            ASSERT(thunk.codeRef);
            state = isCompilationThread() ? LazyThunkState::GeneratedOnCompilationThread : LazyThunkState::Generated;
            thunk.state.store(state, std::memory_order_release);
        }
    }

    if (state == LazyThunkState::GeneratedOnCompilationThread && !isCompilationThread()) {
        // The main thread will issue a crossModifyingCodeFence before running
        // any code the compiler thread generates, including any thunks that they
        // generate. However, the main thread may grab the thunk a compiler thread
        // generated before we've issued that crossModifyingCodeFence. Hence, we
        // conservatively say that when the main thread grabs a thunk generated
        // from a compiler thread for the first time, it issues a crossModifyingCodeFence.
        WTF::crossModifyingCodeFence();
        thunk.state.store(LazyThunkState::Generated, std::memory_order_release);
    }

    return thunk.codeRef;
}

MacroAssemblerCodeRef JITThunks::ctiSlowPathFunctionStub(VM& vm, SlowPathFunction slowPathFunction)
{
    auto key = std::bit_cast(slowPathFunction);
    return ctiStubImpl(key, [&] {
        return JITSlowPathCall::generateThunk(vm, slowPathFunction);
    });
}

struct JITThunks::HostKeySearcher {
    static unsigned NODELETE hash(const HostFunctionKey& key) { return WeakNativeExecutableHash::hash(key); }
    static bool NODELETE equal(const Weak& a, const HostFunctionKey& b) { return WeakNativeExecutableHash::equal(a, b); }
};

struct JITThunks::NativeExecutableTranslator {
    static unsigned NODELETE hash(const NativeExecutable* key) { return WeakNativeExecutableHash::hash(key); }
    static bool NODELETE equal(const Weak& a, const NativeExecutable* b) { return WeakNativeExecutableHash::equal(a, b); }
    static void translate(Weak& location, NativeExecutable* executable, unsigned)
    {
        location = Weak(executable, executable->vm().jitStubs.get());
    }
};

void JITThunks::finalize(Handle handle, void*)
{
    auto* nativeExecutable = static_cast(handle.get().asCell());
    auto hostFunctionKey = std::make_tuple(nativeExecutable->function(), nativeExecutable->constructor(), nativeExecutable->implementationVisibility(), nativeExecutable->length(), nativeExecutable->name());
    {
        AssertNoGC assertNoGC;
        auto iterator = m_nativeExecutableSet.find(hostFunctionKey);
        // Because this finalizer is called, this means that we still have dead Weak in m_nativeExecutableSet.
        ASSERT(iterator != m_nativeExecutableSet.end());
        ASSERT(iterator->unsafeImpl()->state() == WeakImpl::State::Finalized);
        m_nativeExecutableSet.remove(iterator);
    }
}

NativeExecutable* JITThunks::hostFunctionStub(VM& vm, TaggedNativeFunction function, TaggedNativeFunction constructor, ImplementationVisibility implementationVisibility, unsigned length, const String& name)
{
    return hostFunctionStub(vm, function, constructor, nullptr, implementationVisibility, NoIntrinsic, nullptr, length, name);
}

NativeExecutable* JITThunks::hostFunctionStub(VM& vm, TaggedNativeFunction function, TaggedNativeFunction constructor, ThunkGenerator generator, ImplementationVisibility implementationVisibility, Intrinsic intrinsic, const DOMJIT::Signature* signature, unsigned length, const String& name)
{
    ASSERT(!isCompilationThread());
    ASSERT(Options::useJIT());

    auto hostFunctionKey = std::make_tuple(function, constructor, implementationVisibility, length, name);
    {
        AssertNoGC assertNoGC;
        auto iterator = m_nativeExecutableSet.find(hostFunctionKey);
        if (iterator != m_nativeExecutableSet.end()) {
            // It is possible that this returns Weak which is Dead, but not finalized.
            // We should not use this reference to store value created in the subsequent sequence, since allocating NativeExecutable can cause GC, which changes this Set.
            if (auto* executable = iterator->get())
                return executable;
        }
    }

    RefPtr forCall;
    if (generator) {
        MacroAssemblerCodeRef entry = generator(vm).retagged();
        forCall = adoptRef(new DirectJITCode(entry, entry.code(), JITType::HostCallThunk, intrinsic));
    } else if (signature)
        forCall = adoptRef(new NativeDOMJITCode(MacroAssemblerCodeRef::createSelfManagedCodeRef(ctiNativeCall(vm).retagged()), JITType::HostCallThunk, intrinsic, signature));
    else
        forCall = adoptRef(new NativeJITCode(MacroAssemblerCodeRef::createSelfManagedCodeRef(ctiNativeCall(vm).retagged()), JITType::HostCallThunk, intrinsic));

    Ref forConstruct = adoptRef(*new NativeJITCode(MacroAssemblerCodeRef::createSelfManagedCodeRef(ctiNativeConstruct(vm).retagged()), JITType::HostCallThunk, NoIntrinsic));

    NativeExecutable* nativeExecutable = NativeExecutable::create(vm, forCall.releaseNonNull(), function, WTF::move(forConstruct), constructor, implementationVisibility, length, name);
    {
        AssertNoGC assertNoGC;
        auto addResult = m_nativeExecutableSet.add(nativeExecutable);
        if (!addResult.isNewEntry) {
            // Override the existing Weak with the new one since it is dead.
            ASSERT(!*addResult.iterator);
            *addResult.iterator = Weak(nativeExecutable, this);
            ASSERT(*addResult.iterator);
#if ASSERT_ENABLED
            auto iterator = m_nativeExecutableSet.find(hostFunctionKey);
            ASSERT(iterator != m_nativeExecutableSet.end());
            ASSERT(iterator->get() == nativeExecutable);
            ASSERT(iterator->unsafeImpl()->state() == WeakImpl::State::Live);
#endif
        }
    }
    return nativeExecutable;
}

NativeExecutable* JITThunks::hostFunctionStub(VM& vm, TaggedNativeFunction function, ThunkGenerator generator, ImplementationVisibility implementationVisibility, Intrinsic intrinsic, unsigned length, const String& name)
{
    return hostFunctionStub(vm, function, callHostFunctionAsConstructor, generator, implementationVisibility, intrinsic, nullptr, length, name);
}

} // namespace JSC

WTF_ALLOW_UNSAFE_BUFFER_USAGE_END

#endif // ENABLE(JIT)

Web Proxy Viewer  |  New URL  |  Original Page