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

#pragma once

#include "ArithProfile.h"
#include "ArrayProfile.h"
#include "BytecodeConventions.h"
#include "CodeType.h"
#include "DFGExitProfile.h"
#include "ExecutionCounter.h"
#include "ExpressionRangeInfo.h"
#include "HandlerInfo.h"
#include "Identifier.h"
#include "InstructionStream.h"
#include "JSCast.h"
#include "Opcode.h"
#include "ParserModes.h"
#include "RegExp.h"
#include "UnlinkedFunctionExecutable.h"
#include "UnlinkedMetadataTable.h"
#include "ValueProfile.h"
#include "VirtualRegister.h"
#include <algorithm>
#include <wtf/BitVector.h>
#include <wtf/FixedVector.h>
#include <wtf/RobinHoodHashMap.h>
#include <wtf/TriState.h>
#include <wtf/Vector.h>
#include <wtf/text/UniquedStringImpl.h>

namespace JSC {

class BytecodeLivenessAnalysis;
class BytecodeRewriter;
class CodeBlock;
class Debugger;
class FunctionExecutable;
class ParserError;
class ScriptExecutable;
class SourceCode;
class SourceProvider;
class UnlinkedCodeBlock;
class UnlinkedCodeBlockGenerator;
class UnlinkedFunctionCodeBlock;
class UnlinkedFunctionExecutable;
class BaselineJITCode;
struct ExecutableInfo;
enum class LinkTimeConstant : int32_t;

template<typename CodeBlockType>
class CachedCodeBlock;

typedef unsigned UnlinkedArrayAllocationProfile;
typedef unsigned UnlinkedObjectAllocationProfile;

struct UnlinkedStringJumpTable {
    struct OffsetLocation {
        int32_t m_branchOffset;
        unsigned m_indexInTable;
    };

    using StringOffsetTable = MemoryCompactLookupOnlyRobinHoodHashMap<RefPtr<StringImpl>, OffsetLocation>;
    StringOffsetTable m_offsetTable;

    inline int32_t offsetForValue(StringImpl* value, int32_t defaultOffset) const
    {
        auto loc = m_offsetTable.find(value);
        if (loc == m_offsetTable.end())
            return defaultOffset;
        return loc->value.m_branchOffset;
    }

    inline unsigned indexForValue(StringImpl* value, unsigned defaultIndex) const
    {
        auto loc = m_offsetTable.find(value);
        if (loc == m_offsetTable.end())
            return defaultIndex;
        return loc->value.m_indexInTable;
    }
};

struct UnlinkedSimpleJumpTable {
    FixedVector<int32_t> m_branchOffsets;
    int32_t m_min;

    inline int32_t offsetForValue(int32_t value, int32_t defaultOffset) const
    {
        if (value >= m_min && static_cast<uint32_t>(value - m_min) < m_branchOffsets.size()) {
            int32_t offset = m_branchOffsets[value - m_min];
            if (offset)
                return offset;
        }
        return defaultOffset;
    }

    void add(int32_t key, int32_t offset)
    {
        if (!m_branchOffsets[key])
            m_branchOffsets[key] = offset;
    }
};

class UnlinkedCodeBlock : public JSCell {
public:
    typedef JSCell Base;
    static constexpr unsigned StructureFlags = Base::StructureFlags;

    static constexpr bool needsDestruction = true;

    template<typename, SubspaceAccess>
    static void subspaceFor(VM&)
    {
        RELEASE_ASSERT_NOT_REACHED();
    }

    friend class LLIntOffsetsExtractor;

    enum { CallFunction, ApplyFunction };

    void initializeLoopHintExecutionCounter();

    bool isConstructor() const { return m_isConstructor; }
    bool usesCallEval() const { return m_usesCallEval; }
    void setUsesCallEval() { m_usesCallEval = true; }
    SourceParseMode parseMode() const { return m_parseMode; }
    bool isArrowFunction() const { return isArrowFunctionParseMode(parseMode()); }
    DerivedContextType derivedContextType() const { return static_cast<DerivedContextType>(m_derivedContextType); }
    EvalContextType evalContextType() const { return static_cast<EvalContextType>(m_evalContextType); }
    bool isArrowFunctionContext() const { return m_isArrowFunctionContext; }
    bool isClassContext() const { return m_isClassContext; }
    bool hasTailCalls() const { return m_hasTailCalls; }
    void setHasTailCalls() { m_hasTailCalls = true; }
    bool allowDirectEvalCache() const { return !(m_features & NoEvalCacheFeature); }

    bool hasExpressionInfo() { return m_expressionInfo.size(); }
    const FixedVector<ExpressionRangeInfo>& expressionInfo() { return m_expressionInfo; }

    bool hasCheckpoints() const { return m_hasCheckpoints; }
    void setHasCheckpoints() { m_hasCheckpoints = true; }

    // Special registers
    void setThisRegister(VirtualRegister thisRegister) { m_thisRegister = thisRegister; }
    void setScopeRegister(VirtualRegister scopeRegister) { m_scopeRegister = scopeRegister; }

    // Parameter information
    void setNumParameters(int newValue) { m_numParameters = newValue; }
    unsigned numParameters() const { return m_numParameters; }

    // Constant Pools

    size_t numberOfIdentifiers() const { return m_identifiers.size(); }
    const Identifier& identifier(int index) const { return m_identifiers[index]; }
    const FixedVector<Identifier>& identifiers() const { return m_identifiers; }

    BitVector& bitVector(size_t i) { ASSERT(m_rareData); return m_rareData->m_bitVectors[i]; }

    const FixedVector<WriteBarrier<Unknown>>& constantRegisters() { return m_constantRegisters; }
    const WriteBarrier<Unknown>& constantRegister(VirtualRegister reg) const { return m_constantRegisters[reg.toConstantIndex()]; }
    WriteBarrier<Unknown>& constantRegister(VirtualRegister reg) { return m_constantRegisters[reg.toConstantIndex()]; }
    ALWAYS_INLINE JSValue getConstant(VirtualRegister reg) const { return m_constantRegisters[reg.toConstantIndex()].get(); }
    const FixedVector<SourceCodeRepresentation>& constantsSourceCodeRepresentation() { return m_constantsSourceCodeRepresentation; }

    SourceCodeRepresentation constantSourceCodeRepresentation(VirtualRegister reg) const
    {
        return constantSourceCodeRepresentation(reg.toConstantIndex());
    }
    SourceCodeRepresentation constantSourceCodeRepresentation(unsigned index) const
    {
        if (index < m_constantsSourceCodeRepresentation.size())
            return m_constantsSourceCodeRepresentation[index];
        return SourceCodeRepresentation::Other;
    }

    unsigned numberOfConstantIdentifierSets() const { return m_rareData ? m_rareData->m_constantIdentifierSets.size() : 0; }
    const FixedVector<IdentifierSet>& constantIdentifierSets() { ASSERT(m_rareData); return m_rareData->m_constantIdentifierSets; }

    // Jumps
    size_t numberOfJumpTargets() const { return m_jumpTargets.size(); }
    unsigned jumpTarget(int index) const { return m_jumpTargets[index]; }
    unsigned lastJumpTarget() const { return m_jumpTargets.last(); }

    UnlinkedHandlerInfo* handlerForBytecodeIndex(BytecodeIndex, RequiredHandler = RequiredHandler::AnyHandler);
    UnlinkedHandlerInfo* handlerForIndex(unsigned, RequiredHandler = RequiredHandler::AnyHandler);

    bool isBuiltinFunction() const { return m_isBuiltinFunction; }

    ConstructorKind constructorKind() const { return static_cast<ConstructorKind>(m_constructorKind); }
    SuperBinding superBinding() const { return static_cast<SuperBinding>(m_superBinding); }
    JSParserScriptMode scriptMode() const { return static_cast<JSParserScriptMode>(m_scriptMode); }

    const JSInstructionStream& instructions() const;
    const JSInstruction* instructionAt(BytecodeIndex index) const { return instructions().at(index).ptr(); }
    unsigned bytecodeOffset(const JSInstruction* instruction)
    {
        const auto* instructionsBegin = instructions().at(0).ptr();
        const auto* instructionsEnd = reinterpret_cast<const JSInstruction*>(reinterpret_cast<uintptr_t>(instructionsBegin) + instructions().size());
        RELEASE_ASSERT(instruction >= instructionsBegin && instruction < instructionsEnd);
        return instruction - instructionsBegin;
    }
    unsigned instructionsSize() const { return instructions().size(); }

    unsigned numCalleeLocals() const { return m_numCalleeLocals; }
    unsigned numVars() const { return m_numVars; }

    // Jump Tables

    size_t numberOfUnlinkedSwitchJumpTables() const { return m_rareData ? m_rareData->m_unlinkedSwitchJumpTables.size() : 0; }
    const UnlinkedSimpleJumpTable& unlinkedSwitchJumpTable(int tableIndex) const { ASSERT(m_rareData); return m_rareData->m_unlinkedSwitchJumpTables[tableIndex]; }

    size_t numberOfUnlinkedStringSwitchJumpTables() const { return m_rareData ? m_rareData->m_unlinkedStringSwitchJumpTables.size() : 0; }
    const UnlinkedStringJumpTable& unlinkedStringSwitchJumpTable(int tableIndex) const { ASSERT(m_rareData); return m_rareData->m_unlinkedStringSwitchJumpTables[tableIndex]; }

    UnlinkedFunctionExecutable* functionDecl(int index) { return m_functionDecls[index].get(); }
    size_t numberOfFunctionDecls() { return m_functionDecls.size(); }
    UnlinkedFunctionExecutable* functionExpr(int index) { return m_functionExprs[index].get(); }
    size_t numberOfFunctionExprs() { return m_functionExprs.size(); }

    // Exception handling support
    size_t numberOfExceptionHandlers() const { return m_rareData ? m_rareData->m_exceptionHandlers.size() : 0; }
    UnlinkedHandlerInfo& exceptionHandler(int index) { ASSERT(m_rareData); return m_rareData->m_exceptionHandlers[index]; }

    CodeType codeType() const { return static_cast<CodeType>(m_codeType); }

    VirtualRegister thisRegister() const { return m_thisRegister; }
    VirtualRegister scopeRegister() const { return m_scopeRegister; }

    bool hasRareData() const { return m_rareData.get(); }

    int lineNumberForBytecodeIndex(BytecodeIndex);

    void expressionRangeForBytecodeIndex(BytecodeIndex, int& divot,
        int& startOffset, int& endOffset, unsigned& line, unsigned& column) const;

    bool typeProfilerExpressionInfoForBytecodeOffset(unsigned bytecodeOffset, unsigned& startDivot, unsigned& endDivot);

    void recordParse(CodeFeatures features, LexicalScopeFeatures lexicalScopeFeatures, bool hasCapturedVariables, unsigned lineCount, unsigned endColumn)
    {
        m_features = features;
        m_lexicalScopeFeatures = lexicalScopeFeatures;
        m_hasCapturedVariables = hasCapturedVariables;
        m_lineCount = lineCount;
        // For the UnlinkedCodeBlock, startColumn is always 0.
        m_endColumn = endColumn;
    }

    StringImpl* sourceURLDirective() const { return m_sourceURLDirective.get(); }
    StringImpl* sourceMappingURLDirective() const { return m_sourceMappingURLDirective.get(); }
    void setSourceURLDirective(const String& sourceURL) { m_sourceURLDirective = sourceURL.impl(); }
    void setSourceMappingURLDirective(const String& sourceMappingURL) { m_sourceMappingURLDirective = sourceMappingURL.impl(); }

    CodeFeatures codeFeatures() const { return m_features; }
    LexicalScopeFeatures lexicalScopeFeatures() const { return static_cast<LexicalScopeFeatures>(m_lexicalScopeFeatures); }
    bool hasCapturedVariables() const { return m_hasCapturedVariables; }
    unsigned lineCount() const { return m_lineCount; }
    ALWAYS_INLINE unsigned startColumn() const { return 0; }
    unsigned endColumn() const { return m_endColumn; }

    const FixedVector<JSInstructionStream::Offset>& opProfileControlFlowBytecodeOffsets() const
    {
        ASSERT(m_rareData);
        return m_rareData->m_opProfileControlFlowBytecodeOffsets;
    }
    bool hasOpProfileControlFlowBytecodeOffsets() const
    {
        return m_rareData && !m_rareData->m_opProfileControlFlowBytecodeOffsets.isEmpty();
    }

    void dumpExpressionRangeInfo(); // For debugging purpose only.

    bool wasCompiledWithDebuggingOpcodes() const { return m_codeGenerationMode.contains(CodeGenerationMode::Debugger); }
    bool wasCompiledWithTypeProfilerOpcodes() const { return m_codeGenerationMode.contains(CodeGenerationMode::TypeProfiler); }
    bool wasCompiledWithControlFlowProfilerOpcodes() const { return m_codeGenerationMode.contains(CodeGenerationMode::ControlFlowProfiler); }
    OptionSet<CodeGenerationMode> codeGenerationMode() const { return m_codeGenerationMode; }

    TriState didOptimize() const { return static_cast<TriState>(m_didOptimize); }
    void setDidOptimize(TriState didOptimize) { m_didOptimize = static_cast<unsigned>(didOptimize); }

    static constexpr unsigned maxAge = 7;

    unsigned age() const { return m_age; }
    void resetAge() { m_age = 0; }

    NeedsClassFieldInitializer needsClassFieldInitializer() const
    {
        if (m_rareData)
            return static_cast<NeedsClassFieldInitializer>(m_rareData->m_needsClassFieldInitializer);
        return NeedsClassFieldInitializer::No;
    }

    PrivateBrandRequirement privateBrandRequirement() const
    {
        if (m_rareData)
            return static_cast<PrivateBrandRequirement>(m_rareData->m_privateBrandRequirement);
        return PrivateBrandRequirement::None;
    }

    void dump(PrintStream&) const;

    BytecodeLivenessAnalysis& livenessAnalysis(CodeBlock* codeBlock)
    {
        if (m_liveness)
            return *m_liveness;
        return livenessAnalysisSlow(codeBlock);
    }

#if ENABLE(DFG_JIT)
    bool hasExitSite(const ConcurrentJSLocker& locker, const DFG::FrequentExitSite& site) const
    {
        return m_exitProfile.hasExitSite(locker, site);
    }

    bool hasExitSite(const DFG::FrequentExitSite& site)
    {
        ConcurrentJSLocker locker(m_lock);
        return hasExitSite(locker, site);
    }

    DFG::ExitProfile& exitProfile() { return m_exitProfile; }
#endif

    UnlinkedMetadataTable& metadata() { return m_metadata.get(); }

    size_t metadataSizeInBytes()
    {
        return m_metadata->sizeInBytes();
    }

    bool loopHintsAreEligibleForFuzzingEarlyReturn()
    {
        // Some builtins are required to always complete the loops they run.
        return !isBuiltinFunction();
    }
    void allocateSharedProfiles(unsigned numBinaryArithProfiles, unsigned numUnaryArithProfiles);
    UnlinkedValueProfile& unlinkedValueProfile(unsigned index) { return m_valueProfiles[index]; }
    UnlinkedArrayProfile& unlinkedArrayProfile(unsigned index) { return m_arrayProfiles[index]; }
    unsigned numberOfValueProfiles() const { return m_valueProfiles.size(); }
    unsigned numberOfArrayProfiles() const { return m_arrayProfiles.size(); }

#if ASSERT_ENABLED
    bool hasIdentifier(UniquedStringImpl*);
#endif

    int32_t thresholdForJIT(int32_t threshold);

protected:
    UnlinkedCodeBlock(VM&, Structure*, CodeType, const ExecutableInfo&, OptionSet<CodeGenerationMode>);

    template<typename CodeBlockType>
    UnlinkedCodeBlock(Decoder&, Structure*, const CachedCodeBlock<CodeBlockType>&);

    ~UnlinkedCodeBlock();

    void finishCreation(VM& vm)
    {
        Base::finishCreation(vm);
    }

private:
    friend class BytecodeRewriter;
    friend class UnlinkedCodeBlockGenerator;
    template<typename Traits>
    friend class BytecodeGeneratorBase;

    template<typename CodeBlockType>
    friend class CachedCodeBlock;

    void createRareDataIfNecessary(const AbstractLocker&)
    {
        if (!m_rareData)
            m_rareData = makeUnique<RareData>();
    }

    void getLineAndColumn(const ExpressionRangeInfo&, unsigned& line, unsigned& column) const;
    BytecodeLivenessAnalysis& livenessAnalysisSlow(CodeBlock*);


    VirtualRegister m_thisRegister;
    VirtualRegister m_scopeRegister;

    unsigned m_numVars : 31;
    unsigned m_usesCallEval : 1;
    unsigned m_numCalleeLocals : 31;
    unsigned m_isConstructor : 1;
    unsigned m_numParameters : 31;
    unsigned m_hasCapturedVariables : 1;

    unsigned m_isBuiltinFunction : 1;
    unsigned m_superBinding : 1;
    unsigned m_scriptMode: 1;
    unsigned m_isArrowFunctionContext : 1;
    unsigned m_isClassContext : 1;
    unsigned m_hasTailCalls : 1;
    unsigned m_constructorKind : 2;
    unsigned m_derivedContextType : 2;
    unsigned m_evalContextType : 2;
    unsigned m_codeType : 2;
    unsigned m_didOptimize : 2;
    unsigned m_age : 3;
    static_assert(((1U << 3) - 1) >= maxAge);
    bool m_hasCheckpoints : 1;
    unsigned m_lexicalScopeFeatures : 4;
public:
    ConcurrentJSLock m_lock;
#if ENABLE(JIT)
    RefPtr<BaselineJITCode> m_unlinkedBaselineCode;
#endif
private:
    CodeFeatures m_features { 0 };
    SourceParseMode m_parseMode;
    OptionSet<CodeGenerationMode> m_codeGenerationMode;

    unsigned m_lineCount { 0 };
    unsigned m_endColumn { UINT_MAX };

    PackedRefPtr<StringImpl> m_sourceURLDirective;
    PackedRefPtr<StringImpl> m_sourceMappingURLDirective;

    FixedVector<JSInstructionStream::Offset> m_jumpTargets;
    Ref<UnlinkedMetadataTable> m_metadata;
    std::unique_ptr<JSInstructionStream> m_instructions;
    std::unique_ptr<BytecodeLivenessAnalysis> m_liveness;

#if ENABLE(DFG_JIT)
    DFG::ExitProfile m_exitProfile;
#endif

    // Constant Pools
    FixedVector<Identifier> m_identifiers;
    FixedVector<WriteBarrier<Unknown>> m_constantRegisters;
    FixedVector<SourceCodeRepresentation> m_constantsSourceCodeRepresentation;
    using FunctionExpressionVector = FixedVector<WriteBarrier<UnlinkedFunctionExecutable>>;
    FunctionExpressionVector m_functionDecls;
    FunctionExpressionVector m_functionExprs;

public:
    struct RareData {
        WTF_MAKE_STRUCT_FAST_ALLOCATED;

        size_t sizeInBytes(const AbstractLocker&) const;

        FixedVector<UnlinkedHandlerInfo> m_exceptionHandlers;

        // Jump Tables
        FixedVector<UnlinkedSimpleJumpTable> m_unlinkedSwitchJumpTables;
        FixedVector<UnlinkedStringJumpTable> m_unlinkedStringSwitchJumpTables;

        FixedVector<ExpressionRangeInfo::FatPosition> m_expressionInfoFatPositions;

        struct TypeProfilerExpressionRange {
            unsigned m_startDivot;
            unsigned m_endDivot;
        };
        HashMap<unsigned, TypeProfilerExpressionRange> m_typeProfilerInfoMap;
        FixedVector<JSInstructionStream::Offset> m_opProfileControlFlowBytecodeOffsets;
        FixedVector<BitVector> m_bitVectors;
        FixedVector<IdentifierSet> m_constantIdentifierSets;

        unsigned m_needsClassFieldInitializer : 1;
        unsigned m_privateBrandRequirement : 1;
    };

    int outOfLineJumpOffset(JSInstructionStream::Offset);
    int outOfLineJumpOffset(const JSInstructionStream::Ref& instruction)
    {
        return outOfLineJumpOffset(instruction.offset());
    }
    int outOfLineJumpOffset(const JSInstruction* pc)
    {
        unsigned bytecodeOffset = this->bytecodeOffset(pc);
        return outOfLineJumpOffset(bytecodeOffset);
    }

    BinaryArithProfile& binaryArithProfile(unsigned i) { return m_binaryArithProfiles[i]; }
    UnaryArithProfile& unaryArithProfile(unsigned i) { return m_unaryArithProfiles[i]; }

    BaselineExecutionCounter& llintExecuteCounter() { return m_llintExecuteCounter; }

private:
    using OutOfLineJumpTargets = HashMap<JSInstructionStream::Offset, int>;

    OutOfLineJumpTargets m_outOfLineJumpTargets;
    std::unique_ptr<RareData> m_rareData;
    FixedVector<ExpressionRangeInfo> m_expressionInfo;
    BaselineExecutionCounter m_llintExecuteCounter;
    FixedVector<UnlinkedValueProfile> m_valueProfiles;
    FixedVector<UnlinkedArrayProfile> m_arrayProfiles;
    FixedVector<BinaryArithProfile> m_binaryArithProfiles;
    FixedVector<UnaryArithProfile> m_unaryArithProfiles;

#if ASSERT_ENABLED
    Lock m_cachedIdentifierUidsLock;
    HashSet<UniquedStringImpl*> m_cachedIdentifierUids;
#endif

protected:
    DECLARE_VISIT_CHILDREN;
    static size_t estimatedSize(JSCell*, VM&);

public:
    DECLARE_INFO;
};

}
