/*
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *  Copyright (C) 2003, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
 *  Copyright (C) 2007 Cameron Zwarich (cwzwarich@uwaterloo.ca)
 *  Copyright (C) 2007 Maks Orlovich
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 *
 */

#ifndef Arguments_h
#define Arguments_h

#include "CodeOrigin.h"
#include "JSActivation.h"
#include "JSDestructibleObject.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
#include "Interpreter.h"
#include "ObjectConstructor.h"
#include <wtf/StdLibExtras.h>

namespace JSC {

class Arguments : public JSDestructibleObject {
    friend class JIT;
    friend class JSArgumentsIterator;
public:
    typedef JSDestructibleObject Base;

    static Arguments* create(VM& vm, CallFrame* callFrame)
    {
        Arguments* arguments = new (NotNull, allocateCell<Arguments>(vm.heap)) Arguments(callFrame);
        arguments->finishCreation(callFrame);
        return arguments;
    }
        
    static Arguments* create(VM& vm, CallFrame* callFrame, InlineCallFrame* inlineCallFrame)
    {
        Arguments* arguments = new (NotNull, allocateCell<Arguments>(vm.heap)) Arguments(callFrame);
        arguments->finishCreation(callFrame, inlineCallFrame);
        return arguments;
    }

    enum { MaxArguments = 0x10000 };

private:
    enum NoParametersType { NoParameters };
        
    Arguments(CallFrame*);
    Arguments(CallFrame*, NoParametersType);
        
public:
    DECLARE_INFO;

    static void visitChildren(JSCell*, SlotVisitor&);

    void fillArgList(ExecState*, MarkedArgumentBuffer&);

    uint32_t length(ExecState* exec) const 
    {
        if (UNLIKELY(m_overrodeLength))
            return get(exec, exec->propertyNames().length).toUInt32(exec);
        return m_numArguments; 
    }
        
    void copyToArguments(ExecState*, CallFrame*, uint32_t length);
    void tearOff(CallFrame*);
    void tearOff(CallFrame*, InlineCallFrame*);
    bool isTornOff() const { return m_registerArray.get(); }
    void didTearOffActivation(ExecState*, JSActivation*);

    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) 
    { 
        return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); 
    }
    
    static ptrdiff_t offsetOfNumArguments() { return OBJECT_OFFSETOF(Arguments, m_numArguments); }
    static ptrdiff_t offsetOfRegisters() { return OBJECT_OFFSETOF(Arguments, m_registers); }
    static ptrdiff_t offsetOfSlowArgumentData() { return OBJECT_OFFSETOF(Arguments, m_slowArgumentData); }
    static ptrdiff_t offsetOfOverrodeLength() { return OBJECT_OFFSETOF(Arguments, m_overrodeLength); }
    
protected:
    static const unsigned StructureFlags = OverridesGetOwnPropertySlot | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero | OverridesVisitChildren | OverridesGetPropertyNames | JSObject::StructureFlags;

    void finishCreation(CallFrame*);
    void finishCreation(CallFrame*, InlineCallFrame*);

private:
    static void destroy(JSCell*);
    static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&);
    static bool getOwnPropertySlotByIndex(JSObject*, ExecState*, unsigned propertyName, PropertySlot&);
    static void getOwnPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode);
    static void put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&);
    static void putByIndex(JSCell*, ExecState*, unsigned propertyName, JSValue, bool shouldThrow);
    static bool deleteProperty(JSCell*, ExecState*, PropertyName);
    static bool deletePropertyByIndex(JSCell*, ExecState*, unsigned propertyName);
    static bool defineOwnProperty(JSObject*, ExecState*, PropertyName, const PropertyDescriptor&, bool shouldThrow);
    void createStrictModeCallerIfNecessary(ExecState*);
    void createStrictModeCalleeIfNecessary(ExecState*);

    bool isArgument(size_t);
    bool trySetArgument(VM&, size_t argument, JSValue);
    JSValue tryGetArgument(size_t argument);
    bool isDeletedArgument(size_t);
    bool tryDeleteArgument(size_t);
    WriteBarrierBase<Unknown>& argument(size_t);
    void allocateSlowArguments();

    void init(CallFrame*);

    WriteBarrier<JSActivation> m_activation;

    unsigned m_numArguments;

    // We make these full byte booleans to make them easy to test from the JIT,
    // and because even if they were single-bit booleans we still wouldn't save
    // any space.
    bool m_overrodeLength; 
    bool m_overrodeCallee;
    bool m_overrodeCaller;
    bool m_isStrictMode;

    WriteBarrierBase<Unknown>* m_registers;
    std::unique_ptr<WriteBarrier<Unknown>[]> m_registerArray;

    struct SlowArgumentData {
        std::unique_ptr<SlowArgument[]> slowArguments;
        int bytecodeToMachineCaptureOffset; // Add this if you have a bytecode offset into captured registers and you want the machine offset instead. Subtract if you want to do the opposite.
    };
    
    std::unique_ptr<SlowArgumentData> m_slowArgumentData;

    WriteBarrier<JSFunction> m_callee;
};

Arguments* asArguments(JSValue);

inline Arguments* asArguments(JSValue value)
{
    ASSERT(asObject(value)->inherits(Arguments::info()));
    return static_cast<Arguments*>(asObject(value));
}

inline Arguments::Arguments(CallFrame* callFrame)
    : JSDestructibleObject(callFrame->vm(), callFrame->lexicalGlobalObject()->argumentsStructure())
{
}

inline Arguments::Arguments(CallFrame* callFrame, NoParametersType)
    : JSDestructibleObject(callFrame->vm(), callFrame->lexicalGlobalObject()->argumentsStructure())
{
}

inline void Arguments::allocateSlowArguments()
{
    if (m_slowArgumentData)
        return;
    m_slowArgumentData = std::make_unique<SlowArgumentData>();
    m_slowArgumentData->bytecodeToMachineCaptureOffset = 0;
    m_slowArgumentData->slowArguments = std::make_unique<SlowArgument[]>(m_numArguments);
    for (size_t i = 0; i < m_numArguments; ++i) {
        ASSERT(m_slowArgumentData->slowArguments[i].status == SlowArgument::Normal);
        m_slowArgumentData->slowArguments[i].index = CallFrame::argumentOffset(i);
    }
}

inline bool Arguments::tryDeleteArgument(size_t argument)
{
    if (!isArgument(argument))
        return false;
    allocateSlowArguments();
    m_slowArgumentData->slowArguments[argument].status = SlowArgument::Deleted;
    return true;
}

inline bool Arguments::trySetArgument(VM& vm, size_t argument, JSValue value)
{
    if (!isArgument(argument))
        return false;
    this->argument(argument).set(vm, this, value);
    return true;
}

inline JSValue Arguments::tryGetArgument(size_t argument)
{
    if (!isArgument(argument))
        return JSValue();
    return this->argument(argument).get();
}

inline bool Arguments::isDeletedArgument(size_t argument)
{
    if (argument >= m_numArguments)
        return false;
    if (!m_slowArgumentData)
        return false;
    if (m_slowArgumentData->slowArguments[argument].status != SlowArgument::Deleted)
        return false;
    return true;
}

inline bool Arguments::isArgument(size_t argument)
{
    if (argument >= m_numArguments)
        return false;
    if (m_slowArgumentData && m_slowArgumentData->slowArguments[argument].status == SlowArgument::Deleted)
        return false;
    return true;
}

inline WriteBarrierBase<Unknown>& Arguments::argument(size_t argument)
{
    ASSERT(isArgument(argument));
    if (!m_slowArgumentData)
        return m_registers[CallFrame::argumentOffset(argument)];

    int index = m_slowArgumentData->slowArguments[argument].index;
    if (!m_activation || m_slowArgumentData->slowArguments[argument].status != SlowArgument::Captured)
        return m_registers[index];

    return m_activation->registerAt(index - m_slowArgumentData->bytecodeToMachineCaptureOffset);
}

inline void Arguments::finishCreation(CallFrame* callFrame)
{
    Base::finishCreation(callFrame->vm());
    ASSERT(inherits(info()));

    JSFunction* callee = jsCast<JSFunction*>(callFrame->callee());
    m_numArguments = callFrame->argumentCount();
    m_registers = reinterpret_cast<WriteBarrierBase<Unknown>*>(callFrame->registers());
    m_callee.set(callFrame->vm(), this, callee);
    m_overrodeLength = false;
    m_overrodeCallee = false;
    m_overrodeCaller = false;
    m_isStrictMode = callFrame->codeBlock()->isStrictMode();

    CodeBlock* codeBlock = callFrame->codeBlock();
    if (codeBlock->hasSlowArguments()) {
        SymbolTable* symbolTable = codeBlock->symbolTable();
        const SlowArgument* slowArguments = codeBlock->machineSlowArguments();
        allocateSlowArguments();
        size_t count = std::min<unsigned>(m_numArguments, symbolTable->parameterCount());
        for (size_t i = 0; i < count; ++i)
            m_slowArgumentData->slowArguments[i] = slowArguments[i];
        m_slowArgumentData->bytecodeToMachineCaptureOffset =
            codeBlock->framePointerOffsetToGetActivationRegisters();
    }

    // The bytecode generator omits op_tear_off_activation in cases of no
    // declared parameters, so we need to tear off immediately.
    if (m_isStrictMode || !callee->jsExecutable()->parameterCount())
        tearOff(callFrame);
}

inline void Arguments::finishCreation(CallFrame* callFrame, InlineCallFrame* inlineCallFrame)
{
    Base::finishCreation(callFrame->vm());
    ASSERT(inherits(info()));

    JSFunction* callee = inlineCallFrame->calleeForCallFrame(callFrame);
    m_numArguments = inlineCallFrame->arguments.size() - 1;
    
    if (m_numArguments) {
        int offsetForArgumentOne = inlineCallFrame->arguments[1].virtualRegister().offset();
        m_registers = reinterpret_cast<WriteBarrierBase<Unknown>*>(callFrame->registers()) + offsetForArgumentOne - virtualRegisterForArgument(1).offset();
    } else
        m_registers = 0;
    m_callee.set(callFrame->vm(), this, callee);
    m_overrodeLength = false;
    m_overrodeCallee = false;
    m_overrodeCaller = false;
    m_isStrictMode = jsCast<FunctionExecutable*>(inlineCallFrame->executable.get())->isStrictMode();
    ASSERT(!jsCast<FunctionExecutable*>(inlineCallFrame->executable.get())->symbolTable(inlineCallFrame->isCall ? CodeForCall : CodeForConstruct)->slowArguments());

    // The bytecode generator omits op_tear_off_activation in cases of no
    // declared parameters, so we need to tear off immediately.
    if (m_isStrictMode || !callee->jsExecutable()->parameterCount())
        tearOff(callFrame, inlineCallFrame);
}

} // namespace JSC

#endif // Arguments_h
