/*
 * Copyright (C) 2015 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 "InferredType.h"

#include "JSCInlines.h"

namespace JSC {

namespace {

class InferredTypeFireDetail : public FireDetail {
public:
    InferredTypeFireDetail(
        InferredType* inferredType, UniquedStringImpl* uid,
        const InferredType::Descriptor& oldDescriptor, const InferredType::Descriptor& newDescriptor,
        JSValue offendingValue)
        : m_inferredType(inferredType)
        , m_uid(uid)
        , m_oldDescriptor(oldDescriptor)
        , m_newDescriptor(newDescriptor)
        , m_offendingValue(offendingValue)
    {
    }

protected:
    void dump(PrintStream& out) const override
    {
        out.print(
            "Inferred type changed on ", RawPointer(m_inferredType), " for property ", m_uid, ": "
            "old type was ", m_oldDescriptor, " while desired type is ", m_newDescriptor);
        if (m_offendingValue)
            out.print(" due to ", m_offendingValue);
    }

private:
    InferredType* m_inferredType;
    RefPtr<UniquedStringImpl> m_uid;
    InferredType::Descriptor m_oldDescriptor;
    InferredType::Descriptor m_newDescriptor;
    JSValue m_offendingValue;
};

} // anonymous namespace

const ClassInfo InferredType::s_info = { "InferredType", 0, 0, CREATE_METHOD_TABLE(InferredType) };

InferredType* InferredType::create(VM& vm)
{
    InferredType* result = new (NotNull, allocateCell<InferredType>(vm.heap)) InferredType(vm);
    result->finishCreation(vm);
    return result;
}

void InferredType::destroy(JSCell* cell)
{
    InferredType* inferredType = static_cast<InferredType*>(cell);
    inferredType->InferredType::~InferredType();
}

Structure* InferredType::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
    return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info());
}

void InferredType::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
    InferredType* inferredType = jsCast<InferredType*>(cell);

    if (inferredType->m_structure)
        visitor.addUnconditionalFinalizer(&inferredType->m_structure->m_finalizer);
}

InferredType::Kind InferredType::kindForFlags(PutByIdFlags flags)
{
    switch (flags & PutByIdPrimaryTypeMask) {
    case PutByIdPrimaryTypeSecondary:
        switch (flags & PutByIdSecondaryTypeMask) {
        case PutByIdSecondaryTypeBottom:
            return Bottom;
        case PutByIdSecondaryTypeBoolean:
            return Boolean;
        case PutByIdSecondaryTypeOther:
            return Other;
        case PutByIdSecondaryTypeInt32:
            return Int32;
        case PutByIdSecondaryTypeNumber:
            return Number;
        case PutByIdSecondaryTypeString:
            return String;
        case PutByIdSecondaryTypeSymbol:
            return Symbol;
        case PutByIdSecondaryTypeObject:
            return Object;
        case PutByIdSecondaryTypeObjectOrOther:
            return ObjectOrOther;
        case PutByIdSecondaryTypeTop:
            return Top;
        default:
            RELEASE_ASSERT_NOT_REACHED();
            return Bottom;
        }
    case PutByIdPrimaryTypeObjectWithStructure:
        return ObjectWithStructure;
    case PutByIdPrimaryTypeObjectWithStructureOrOther:
        return ObjectWithStructureOrOther;
    default:
        RELEASE_ASSERT_NOT_REACHED();
        return Bottom;
    }
}

InferredType::Descriptor InferredType::Descriptor::forValue(JSValue value)
{
    if (value.isBoolean())
        return Boolean;
    if (value.isUndefinedOrNull())
        return Other;
    if (value.isInt32())
        return Int32;
    if (value.isNumber())
        return Number;
    if (value.isCell()) {
        JSCell* cell = value.asCell();
        if (cell->isString())
            return String;
        if (cell->isSymbol())
            return Symbol;
        if (cell->isObject()) {
            if (cell->structure()->transitionWatchpointSetIsStillValid())
                return Descriptor(ObjectWithStructure, cell->structure());
            return Object;
        }
    }
    return Top;
}

InferredType::Descriptor InferredType::Descriptor::forFlags(VM& vm, PutByIdFlags flags)
{
    Kind kind = kindForFlags(flags);
    Structure* structure;
    if (hasStructure(kind))
        structure = vm.heap.structureIDTable().get(decodeStructureID(flags));
    else
        structure = nullptr;
    return Descriptor(kind, structure);
}

PutByIdFlags InferredType::Descriptor::putByIdFlags() const
{
    switch (m_kind) {
    case Bottom:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeBottom);
    case Boolean:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeBoolean);
    case Other:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeOther);
    case Int32:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeInt32);
    case Number:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeNumber);
    case String:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeString);
    case Symbol:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeSymbol);
    case Object:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeObject);
    case ObjectOrOther:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeObjectOrOther);
    case Top:
        return static_cast<PutByIdFlags>(PutByIdPrimaryTypeSecondary | PutByIdSecondaryTypeTop);
    case ObjectWithStructure:
        return static_cast<PutByIdFlags>(
            PutByIdPrimaryTypeObjectWithStructure | encodeStructureID(m_structure->id()));
    case ObjectWithStructureOrOther:
        return static_cast<PutByIdFlags>(
            PutByIdPrimaryTypeObjectWithStructureOrOther | encodeStructureID(m_structure->id()));
    }
    RELEASE_ASSERT_NOT_REACHED();
    return PutByIdNone;
}

void InferredType::Descriptor::merge(const Descriptor& other)
{
    // Filter out common things to simplify the switch statement below.
    if (*this == other)
        return;
    if (other.m_kind == Bottom)
        return;

    switch (m_kind) {
    case Bottom:
        *this = other;
        return;
    case Boolean:
    case String:
    case Symbol:
        *this = Top;
        return;
    case Other:
        switch (other.m_kind) {
        case ObjectWithStructure:
        case ObjectWithStructureOrOther:
            *this = Descriptor(ObjectWithStructureOrOther, other.structure());
            return;
        case Object:
        case ObjectOrOther:
            *this = ObjectOrOther;
            return;
        default:
            *this = Top;
            return;
        }
    case Int32:
        switch (other.m_kind) {
        case Number:
            *this = Number;
            return;
        default:
            *this = Top;
            return;
        }
    case Number:
        switch (other.m_kind) {
        case Int32:
            return;
        default:
            *this = Top;
            return;
        }
    case ObjectWithStructure:
        switch (other.m_kind) {
        case ObjectWithStructure: // If we see this here, then we know that the structures didn't match.
        case Object:
            *this = Object;
            return;
        case ObjectWithStructureOrOther:
            if (m_structure == other.m_structure) {
                *this = other;
                return;
            }
            *this = ObjectOrOther;
            return;
        case ObjectOrOther:
            *this = ObjectOrOther;
            return;
        case Other:
            m_kind = ObjectWithStructureOrOther;
            return;
        default:
            *this = Top;
            return;
        }
    case ObjectWithStructureOrOther:
        switch (other.m_kind) {
        case ObjectWithStructure:
            if (m_structure == other.m_structure)
                return;
            *this = ObjectOrOther;
            return;
        case Object:
        case ObjectWithStructureOrOther: // If we see this here, then we know that the structures didn't match.
        case ObjectOrOther:
            *this = ObjectOrOther;
            return;
        case Other:
            return;
        default:
            *this = Top;
            return;
        }
    case Object:
        switch (other.m_kind) {
        case ObjectWithStructure:
            return;
        case ObjectWithStructureOrOther:
        case ObjectOrOther:
        case Other:
            *this = ObjectOrOther;
            return;
        default:
            *this = Top;
            return;
        }
    case ObjectOrOther:
        switch (other.m_kind) {
        case ObjectWithStructure:
        case ObjectWithStructureOrOther:
        case Object:
        case Other:
            return;
        default:
            *this = Top;
            return;
        }
    case Top:
        return;
    }

    RELEASE_ASSERT_NOT_REACHED();
}

void InferredType::Descriptor::removeStructure()
{
    switch (m_kind) {
    case ObjectWithStructure:
        *this = Object;
        return;
    case ObjectWithStructureOrOther:
        *this = ObjectOrOther;
        return;
    default:
        return;
    }
}

bool InferredType::Descriptor::subsumes(const Descriptor& other) const
{
    Descriptor merged = *this;
    merged.merge(other);
    return *this == merged;
}

void InferredType::Descriptor::dumpInContext(PrintStream& out, DumpContext* context) const
{
    out.print(m_kind);
    if (m_structure)
        out.print(":", inContext(*m_structure, context));
}

void InferredType::Descriptor::dump(PrintStream& out) const
{
    dumpInContext(out, nullptr);
}

InferredType::InferredType(VM& vm)
    : Base(vm, vm.inferredTypeStructure.get())
    , m_watchpointSet(ClearWatchpoint)
{
}

InferredType::~InferredType()
{
}

bool InferredType::canWatch(const ConcurrentJITLocker& locker, const Descriptor& expected)
{
    if (expected.kind() == Top)
        return false;
    
    return descriptor(locker) == expected;
}

bool InferredType::canWatch(const Descriptor& expected)
{
    ConcurrentJITLocker locker(m_lock);
    return canWatch(locker, expected);
}

void InferredType::addWatchpoint(const ConcurrentJITLocker& locker, Watchpoint* watchpoint)
{
    RELEASE_ASSERT(descriptor(locker).kind() != Top);

    m_watchpointSet.add(watchpoint);
}

void InferredType::addWatchpoint(Watchpoint* watchpoint)
{
    ConcurrentJITLocker locker(m_lock);
    addWatchpoint(locker, watchpoint);
}

void InferredType::dump(PrintStream& out) const
{
    out.print(RawPointer(this), ":", descriptor());
}

bool InferredType::willStoreValueSlow(VM& vm, PropertyName propertyName, JSValue value)
{
    Descriptor oldType;
    Descriptor myType;
    bool result;
    {
        ConcurrentJITLocker locker(m_lock);
        oldType = descriptor(locker);
        myType = Descriptor::forValue(value);

        myType.merge(oldType);
        
        ASSERT(oldType != myType); // The type must have changed if we're on the slow path.

        bool setResult = set(locker, vm, myType);
        result = kind(locker) != Top;
        if (!setResult)
            return result;
    }
    
    InferredTypeFireDetail detail(this, propertyName.uid(), oldType, myType, value);
    m_watchpointSet.fireAll(detail);
    return result;
}

void InferredType::makeTopSlow(VM& vm, PropertyName propertyName)
{
    Descriptor oldType;
    {
        ConcurrentJITLocker locker(m_lock);
        oldType = descriptor(locker);
        if (!set(locker, vm, Top))
            return;
    }

    InferredTypeFireDetail detail(this, propertyName.uid(), oldType, Top, JSValue());
    m_watchpointSet.fireAll(detail);
}

bool InferredType::set(const ConcurrentJITLocker& locker, VM& vm, Descriptor newDescriptor)
{
    // We will trigger write barriers while holding our lock. Currently, write barriers don't GC, but that
    // could change. If it does, we don't want to deadlock. Note that we could have used
    // GCSafeConcurrentJITLocker in the caller, but the caller is on a fast path so maybe that wouldn't be
    // a good idea.
    DeferGCForAWhile deferGC(vm.heap);
    
    // Be defensive: if we're not really changing the type, then we don't have to do anything.
    if (descriptor(locker) == newDescriptor)
        return false;

    bool shouldFireWatchpointSet = false;
    
    // The new descriptor must be more general than the previous one.
    ASSERT(newDescriptor.subsumes(descriptor(locker)));

    // If the new descriptors have different structures, then it can only be because one is null.
    if (descriptor(locker).structure() != newDescriptor.structure())
        ASSERT(!descriptor(locker).structure() || !newDescriptor.structure());

    // We are changing the type, so make sure that if anyone was watching, they find out about it now. If
    // anyone is watching, we make sure to go to Top so that we don't do this sort of thing again.
    if (m_watchpointSet.state() != ClearWatchpoint) {
        // We cannot have been invalidated, since if we were, then we'd already be at Top.
        ASSERT(m_watchpointSet.state() != IsInvalidated);

        // We're about to do expensive things because some compiler thread decided to watch this type and
        // then the type changed. Assume that this property is crazy, and don't ever do any more things for
        // it.
        newDescriptor = Top;

        shouldFireWatchpointSet = true;
    }

    // Remove the old InferredStructure object if we no longer need it.
    if (!newDescriptor.structure())
        m_structure = nullptr;

    // Add a new InferredStructure object if we need one now.
    if (newDescriptor.structure()) {
        if (m_structure) {
            // We should agree on the structures if we get here.
            ASSERT(newDescriptor.structure() == m_structure->structure());
        } else {
            m_structure = std::make_unique<InferredStructure>(vm, this, newDescriptor.structure());
            newDescriptor.structure()->addTransitionWatchpoint(&m_structure->m_watchpoint);
        }
    }

    // Finally, set the descriptor kind.
    m_kind = newDescriptor.kind();

    // Assert that we did things.
    ASSERT(descriptor(locker) == newDescriptor);

    return shouldFireWatchpointSet;
}

void InferredType::removeStructure()
{
    // FIXME: Find an elegant and cheap way to thread information about why we got here into the fire
    // detail in set().
    
    VM& vm = *Heap::heap(this)->vm();

    Descriptor oldDescriptor;
    Descriptor newDescriptor;
    {
        ConcurrentJITLocker locker(m_lock);
        oldDescriptor = descriptor(locker);
        newDescriptor = oldDescriptor;
        newDescriptor.removeStructure();
        
        if (!set(locker, vm, newDescriptor))
            return;
    }

    InferredTypeFireDetail detail(this, nullptr, oldDescriptor, newDescriptor, JSValue());
    m_watchpointSet.fireAll(detail);
}

void InferredType::InferredStructureWatchpoint::fireInternal(const FireDetail&)
{
    InferredStructure* inferredStructure =
        bitwise_cast<InferredStructure*>(
            bitwise_cast<char*>(this) - OBJECT_OFFSETOF(InferredStructure, m_watchpoint));

    inferredStructure->m_parent->removeStructure();
}

void InferredType::InferredStructureFinalizer::finalizeUnconditionally()
{
    InferredStructure* inferredStructure =
        bitwise_cast<InferredStructure*>(
            bitwise_cast<char*>(this) - OBJECT_OFFSETOF(InferredStructure, m_finalizer));

    ASSERT(Heap::isMarked(inferredStructure->m_parent));
    
    if (!Heap::isMarked(inferredStructure->m_structure.get()))
        inferredStructure->m_parent->removeStructure();
}

InferredType::InferredStructure::InferredStructure(VM& vm, InferredType* parent, Structure* structure)
    : m_parent(parent)
    , m_structure(vm, parent, structure)
{
}

} // namespace JSC

namespace WTF {

using namespace JSC;

void printInternal(PrintStream& out, InferredType::Kind kind)
{
    switch (kind) {
    case InferredType::Bottom:
        out.print("Bottom");
        return;
    case InferredType::Boolean:
        out.print("Boolean");
        return;
    case InferredType::Other:
        out.print("Other");
        return;
    case InferredType::Int32:
        out.print("Int32");
        return;
    case InferredType::Number:
        out.print("Number");
        return;
    case InferredType::String:
        out.print("String");
        return;
    case InferredType::Symbol:
        out.print("Symbol");
        return;
    case InferredType::ObjectWithStructure:
        out.print("ObjectWithStructure");
        return;
    case InferredType::ObjectWithStructureOrOther:
        out.print("ObjectWithStructureOrOther");
        return;
    case InferredType::Object:
        out.print("Object");
        return;
    case InferredType::ObjectOrOther:
        out.print("ObjectOrOther");
        return;
    case InferredType::Top:
        out.print("Top");
        return;
    }

    RELEASE_ASSERT_NOT_REACHED();
}

} // namespace WTF

