/*
 * Copyright (C) 2009-2022 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. 
 */

#pragma once

#include "JSFunction.h"
#include "ScriptExecutable.h"
#include "SourceCode.h"
#include <wtf/Box.h>
#include <wtf/Markable.h>

namespace JSC {

struct FunctionOverrideInfo;

class FunctionExecutable final : public ScriptExecutable {
    friend class JIT;
    friend class LLIntOffsetsExtractor;
public:
    typedef ScriptExecutable Base;
    static constexpr unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;

    template<typename CellType, SubspaceAccess>
    static GCClient::IsoSubspace* subspaceFor(VM& vm)
    {
        return &vm.functionExecutableSpace();
    }

    static FunctionExecutable* create(VM& vm, ScriptExecutable* topLevelExecutable, const SourceCode& source, UnlinkedFunctionExecutable* unlinkedExecutable, Intrinsic intrinsic, bool isInsideOrdinaryFunction)
    {
        FunctionExecutable* executable = new (NotNull, allocateCell<FunctionExecutable>(vm)) FunctionExecutable(vm, source, unlinkedExecutable, intrinsic, isInsideOrdinaryFunction);
        executable->finishCreation(vm, topLevelExecutable);
        return executable;
    }
    static FunctionExecutable* fromGlobalCode(
        const Identifier& name, JSGlobalObject*, const SourceCode&, 
        JSObject*& exception, int overrideLineNumber, std::optional<int> functionConstructorParametersEndPosition);

    static void destroy(JSCell*);
        
    UnlinkedFunctionExecutable* unlinkedExecutable() const
    {
        return m_unlinkedExecutable.get();
    }

    // Returns either call or construct bytecode. This can be appropriate
    // for answering questions that that don't vary between call and construct --
    // for example, argumentsRegister().
    FunctionCodeBlock* eitherCodeBlock() const
    {
        if (auto* result = codeBlockForCall())
            return result;
        return codeBlockForConstruct();
    }
        
    bool isGeneratedForCall() const
    {
        return !!codeBlockForCall();
    }

    FunctionCodeBlock* codeBlockForCall() const
    {
        return bitwise_cast<FunctionCodeBlock*>(m_codeBlockForCall.get());
    }

    bool isGeneratedForConstruct() const
    {
        return !!codeBlockForConstruct();
    }

    FunctionCodeBlock* codeBlockForConstruct() const
    {
        return bitwise_cast<FunctionCodeBlock*>(m_codeBlockForConstruct.get());
    }
        
    bool isGeneratedFor(CodeSpecializationKind kind)
    {
        if (kind == CodeForCall)
            return isGeneratedForCall();
        ASSERT(kind == CodeForConstruct);
        return isGeneratedForConstruct();
    }
        
    FunctionCodeBlock* codeBlockFor(CodeSpecializationKind kind)
    {
        if (kind == CodeForCall)
            return codeBlockForCall();
        ASSERT(kind == CodeForConstruct);
        return codeBlockForConstruct();
    }

    FunctionCodeBlock* baselineCodeBlockFor(CodeSpecializationKind);
        
    FunctionCodeBlock* profiledCodeBlockFor(CodeSpecializationKind kind)
    {
        return baselineCodeBlockFor(kind);
    }

    FunctionCodeBlock* replaceCodeBlockWith(VM&, CodeSpecializationKind, CodeBlock*);

    RefPtr<TypeSet> returnStatementTypeSet() 
    {
        RareData& rareData = ensureRareData();
        if (!rareData.m_returnStatementTypeSet)
            rareData.m_returnStatementTypeSet = TypeSet::create();
        return rareData.m_returnStatementTypeSet;
    }
        
    FunctionMode functionMode() { return m_unlinkedExecutable->functionMode(); }
    bool isBuiltinFunction() const { return m_unlinkedExecutable->isBuiltinFunction(); }
    ConstructAbility constructAbility() const { return m_unlinkedExecutable->constructAbility(); }
    bool isClass() const { return m_unlinkedExecutable->isClass(); }
    bool isArrowFunction() const { return parseMode() == SourceParseMode::ArrowFunctionMode; }
    bool isGetter() const { return parseMode() == SourceParseMode::GetterMode; }
    bool isSetter() const { return parseMode() == SourceParseMode::SetterMode; }
    bool isGenerator() const { return isGeneratorParseMode(parseMode()); }
    bool isAsyncGenerator() const { return isAsyncGeneratorParseMode(parseMode()); }
    bool isMethod() const { return parseMode() == SourceParseMode::MethodMode; }
    bool hasPrototypeProperty() const
    {
        return SourceParseModeSet(
            SourceParseMode::NormalFunctionMode,
            SourceParseMode::GeneratorBodyMode,
            SourceParseMode::GeneratorWrapperFunctionMode,
            SourceParseMode::GeneratorWrapperMethodMode,
            SourceParseMode::AsyncGeneratorWrapperFunctionMode,
            SourceParseMode::AsyncGeneratorWrapperMethodMode,
            SourceParseMode::AsyncGeneratorBodyMode
        ).contains(parseMode()) || isClass();
    }
    DerivedContextType derivedContextType() const { return m_unlinkedExecutable->derivedContextType(); }
    bool isClassConstructorFunction() const { return m_unlinkedExecutable->isClassConstructorFunction(); }
    const Identifier& name() { return m_unlinkedExecutable->name(); }
    const Identifier& ecmaName() { return m_unlinkedExecutable->ecmaName(); }
    unsigned parameterCount() const { return m_unlinkedExecutable->parameterCount(); } // Excluding 'this'!
    SourceParseMode parseMode() const { return m_unlinkedExecutable->parseMode(); }
    JSParserScriptMode scriptMode() const { return m_unlinkedExecutable->scriptMode(); }
    SourceCode classSource() const { return m_unlinkedExecutable->classSource(); }

    DECLARE_VISIT_CHILDREN;
    DECLARE_VISIT_OUTPUT_CONSTRAINTS;
    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue proto)
    {
        return Structure::create(vm, globalObject, proto, TypeInfo(FunctionExecutableType, StructureFlags), info());
    }

    void setOverrideLineNumber(int overrideLineNumber)
    {
        if (overrideLineNumber == -1) {
            if (UNLIKELY(m_rareData))
                m_rareData->m_overrideLineNumber = std::nullopt;
            return;
        }
        ensureRareData().m_overrideLineNumber = overrideLineNumber;
    }

    std::optional<int> overrideLineNumber() const
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_overrideLineNumber;
        return std::nullopt;
    }

    int lineCount() const
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_lineCount;
        return m_unlinkedExecutable->lineCount();
    }

    int endColumn() const
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_endColumn;
        return m_unlinkedExecutable->linkedEndColumn(m_source.startColumn().oneBasedInt());
    }

    int firstLine() const
    {
        return source().firstLine().oneBasedInt();
    }

    int lastLine() const
    {
        return firstLine() + lineCount();
    }

    unsigned typeProfilingStartOffset(VM&) const
    {
        return typeProfilingStartOffset();
    }

    unsigned typeProfilingStartOffset() const
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_typeProfilingStartOffset;
        return m_unlinkedExecutable->typeProfilingStartOffset();
    }

    unsigned typeProfilingEndOffset(VM&) const
    {
        return typeProfilingEndOffset();
    }

    unsigned typeProfilingEndOffset() const
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_typeProfilingEndOffset;
        return m_unlinkedExecutable->typeProfilingEndOffset();
    }

    unsigned parametersStartOffset() const
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_parametersStartOffset;
        return m_unlinkedExecutable->parametersStartOffset();
    }

    void overrideInfo(const FunctionOverrideInfo&);

    DECLARE_INFO;

    InferredValue<JSFunction>& singleton()
    {
        return m_singleton;
    }

    void notifyCreation(VM& vm, JSFunction* function, const char* reason)
    {
        m_singleton.notifyWrite(vm, this, function, reason);
    }

    // Cached poly proto structure for the result of constructing this executable.
    Structure* cachedPolyProtoStructure()
    {
        if (UNLIKELY(m_rareData))
            return m_rareData->m_cachedPolyProtoStructureID.get();
        return nullptr;
    }
    void setCachedPolyProtoStructure(VM& vm, Structure* structure)
    {
        ensureRareData().m_cachedPolyProtoStructureID.set(vm, this, structure);
    }

    InlineWatchpointSet& ensurePolyProtoWatchpoint()
    {
        if (!m_polyProtoWatchpoint)
            m_polyProtoWatchpoint = Box<InlineWatchpointSet>::create(IsWatched);
        return *m_polyProtoWatchpoint;
    }

    Box<InlineWatchpointSet> sharedPolyProtoWatchpoint() const { return m_polyProtoWatchpoint; }

    ScriptExecutable* topLevelExecutable() const { return m_topLevelExecutable.get(); }

    TemplateObjectMap& ensureTemplateObjectMap(VM&);

    void finalizeUnconditionally(VM&);

    JSString* toString(JSGlobalObject*);
    JSString* asStringConcurrently() const
    {
        if (!m_rareData)
            return nullptr;
        return m_rareData->m_asString.get();
    }

    static inline ptrdiff_t offsetOfRareData() { return OBJECT_OFFSETOF(FunctionExecutable, m_rareData); }
    static inline ptrdiff_t offsetOfCodeBlockForCall() { return OBJECT_OFFSETOF(FunctionExecutable, m_codeBlockForCall); }
    static inline ptrdiff_t offsetOfCodeBlockForConstruct() { return OBJECT_OFFSETOF(FunctionExecutable, m_codeBlockForConstruct); }

    static ptrdiff_t offsetOfCodeBlockFor(CodeSpecializationKind kind)
    {
        switch (kind) {
        case CodeForCall:
            return OBJECT_OFFSETOF(FunctionExecutable, m_codeBlockForCall);
        case CodeForConstruct:
            return OBJECT_OFFSETOF(FunctionExecutable, m_codeBlockForConstruct);
        }
        RELEASE_ASSERT_NOT_REACHED();
        return 0;
    }

    struct RareData {
        WTF_MAKE_STRUCT_FAST_ALLOCATED;

        static inline ptrdiff_t offsetOfAsString() { return OBJECT_OFFSETOF(RareData, m_asString); }

        RefPtr<TypeSet> m_returnStatementTypeSet;
        unsigned m_lineCount;
        unsigned m_endColumn;
        Markable<int, IntegralMarkableTraits<int, -1>> m_overrideLineNumber;
        unsigned m_parametersStartOffset { 0 };
        unsigned m_typeProfilingStartOffset { UINT_MAX };
        unsigned m_typeProfilingEndOffset { UINT_MAX };
        WriteBarrierStructureID m_cachedPolyProtoStructureID;
        std::unique_ptr<TemplateObjectMap> m_templateObjectMap;
        WriteBarrier<JSString> m_asString;
    };

private:
    friend class ExecutableBase;
    FunctionExecutable(VM&, const SourceCode&, UnlinkedFunctionExecutable*, Intrinsic, bool isInsideOrdinaryFunction);
    
    void finishCreation(VM&, ScriptExecutable* topLevelExecutable);

    friend class ScriptExecutable;

    RareData& ensureRareData()
    {
        if (LIKELY(m_rareData))
            return *m_rareData;
        return ensureRareDataSlow();
    }
    RareData& ensureRareDataSlow();

    JSString* toStringSlow(JSGlobalObject*);

    // FIXME: We can merge rareData pointer and top-level executable pointer. First time, setting parent.
    // If RareData is required, materialize RareData, swap it, and store top-level executable pointer inside RareData.
    // https://bugs.webkit.org/show_bug.cgi?id=197625
    std::unique_ptr<RareData> m_rareData;
    WriteBarrier<ScriptExecutable> m_topLevelExecutable;
    WriteBarrier<UnlinkedFunctionExecutable> m_unlinkedExecutable;
    WriteBarrier<CodeBlock> m_codeBlockForCall;
    WriteBarrier<CodeBlock> m_codeBlockForConstruct;
    InferredValue<JSFunction> m_singleton;
    Box<InlineWatchpointSet> m_polyProtoWatchpoint;
};

} // namespace JSC
