/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "jit/TypedObjectPrediction.h"

using namespace js;
using namespace jit;

static const size_t ALL_FIELDS = SIZE_MAX;

// Sets the prediction to be the common prefix of descrA and descrB,
// considering at most the first max fields.
//
// In the case where the current prediction is a specific struct,
// and we are now seeing a second struct, then descrA and descrB will be
// the current and new struct and max will be ALL_FIELDS.
//
// In the case where the current prediction is already a prefix, and
// we are now seeing an additional struct, then descrA will be the
// current struct and max will be the current prefix length, and
// descrB will be the new struct.
//
// (Note that in general it is not important which struct is passed as
// descrA and which struct is passed as descrB, as the operation is
// symmetric.)
void
TypedObjectPrediction::markAsCommonPrefix(const StructTypeDescr& descrA,
                                          const StructTypeDescr& descrB,
                                          size_t max)
{
    // count is the number of fields in common. It begins as the min
    // of the number of fields from descrA, descrB, and max, and then
    // is decremented as we find uncommon fields.
    if (max > descrA.fieldCount())
        max = descrA.fieldCount();
    if (max > descrB.fieldCount())
        max = descrB.fieldCount();

    size_t i = 0;
    for (; i < max; i++) {
        if (&descrA.fieldName(i) != &descrB.fieldName(i))
            break;
        if (&descrA.fieldDescr(i) != &descrB.fieldDescr(i))
            break;
        MOZ_ASSERT(descrA.fieldOffset(i) == descrB.fieldOffset(i));
    }

    if (i == 0) {
        // empty prefix is not particularly useful.
        markInconsistent();
    } else {
        setPrefix(descrA, i);
    }
}

void
TypedObjectPrediction::addDescr(const TypeDescr& descr)
{
    switch (predictionKind()) {
      case Empty:
        return setDescr(descr);

      case Inconsistent:
        return; // keep same state

      case Descr: {
        if (&descr == data_.descr)
            return; // keep same state

        if (descr.kind() != data_.descr->kind())
            return markInconsistent();

        if (descr.kind() != type::Struct)
            return markInconsistent();

        const StructTypeDescr& structDescr = descr.as<StructTypeDescr>();
        const StructTypeDescr& currentDescr = data_.descr->as<StructTypeDescr>();
        markAsCommonPrefix(structDescr, currentDescr, ALL_FIELDS);
        return;
      }

      case Prefix:
        if (descr.kind() != type::Struct)
            return markInconsistent();

        markAsCommonPrefix(*data_.prefix.descr,
                           descr.as<StructTypeDescr>(),
                           data_.prefix.fields);
        return;
    }

    MOZ_CRASH("Bad predictionKind");
}

type::Kind
TypedObjectPrediction::kind() const
{
    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        break;

      case TypedObjectPrediction::Descr:
        return descr().kind();

      case TypedObjectPrediction::Prefix:
        return prefix().descr->kind();
    }

    MOZ_CRASH("Bad prediction kind");
}

bool
TypedObjectPrediction::ofArrayKind() const
{
    switch (kind()) {
      case type::Scalar:
      case type::Reference:
      case type::Simd:
      case type::Struct:
        return false;

      case type::Array:
        return true;
    }

    MOZ_CRASH("Bad kind");
}

bool
TypedObjectPrediction::hasKnownSize(uint32_t* out) const
{
    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        return false;

      case TypedObjectPrediction::Descr:
        *out = descr().size();
        return true;

      case TypedObjectPrediction::Prefix:
        // We only know a prefix of the struct fields, hence we do not
        // know its complete size.
        return false;

      default:
        MOZ_CRASH("Bad prediction kind");
    }
}

const TypedProto*
TypedObjectPrediction::getKnownPrototype() const
{
    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        return nullptr;

      case TypedObjectPrediction::Descr:
        if (descr().is<ComplexTypeDescr>())
            return &descr().as<ComplexTypeDescr>().instancePrototype();
        return nullptr;

      case TypedObjectPrediction::Prefix:
        // We only know a prefix of the struct fields, hence we cannot
        // say for certain what its prototype will be.
        return nullptr;

      default:
        MOZ_CRASH("Bad prediction kind");
    }
}

template<typename T>
typename T::Type
TypedObjectPrediction::extractType() const
{
    MOZ_ASSERT(kind() == T::Kind);
    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        break;

      case TypedObjectPrediction::Descr:
        return descr().as<T>().type();

      case TypedObjectPrediction::Prefix:
        break; // Prefixes are always structs, never scalars etc
    }

    MOZ_CRASH("Bad prediction kind");
}

ScalarTypeDescr::Type
TypedObjectPrediction::scalarType() const
{
    return extractType<ScalarTypeDescr>();
}

ReferenceTypeDescr::Type
TypedObjectPrediction::referenceType() const
{
    return extractType<ReferenceTypeDescr>();
}

SimdType
TypedObjectPrediction::simdType() const
{
    return descr().as<SimdTypeDescr>().type();
}

bool
TypedObjectPrediction::hasKnownArrayLength(int32_t* length) const
{
    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        return false;

      case TypedObjectPrediction::Descr:
        // In later patches, this condition will always be true
        // so long as this represents an array
        if (descr().is<ArrayTypeDescr>()) {
            *length = descr().as<ArrayTypeDescr>().length();
            return true;
        }
        return false;

      case TypedObjectPrediction::Prefix:
        // Prefixes are always structs, never arrays
        return false;

      default:
        MOZ_CRASH("Bad prediction kind");
    }
}

TypedObjectPrediction
TypedObjectPrediction::arrayElementType() const
{
    MOZ_ASSERT(ofArrayKind());
    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        break;

      case TypedObjectPrediction::Descr:
        return TypedObjectPrediction(descr().as<ArrayTypeDescr>().elementType());

      case TypedObjectPrediction::Prefix:
        break; // Prefixes are always structs, never arrays
    }
    MOZ_CRASH("Bad prediction kind");
}

bool
TypedObjectPrediction::hasFieldNamedPrefix(const StructTypeDescr& descr,
                                           size_t fieldCount,
                                           jsid id,
                                           size_t* fieldOffset,
                                           TypedObjectPrediction* out,
                                           size_t* index) const
{
    // Find the index of the field |id| if any.
    if (!descr.fieldIndex(id, index))
        return false;

    // Check whether the index falls within our known safe prefix.
    if (*index >= fieldCount)
        return false;

    // Load the offset and type.
    *fieldOffset = descr.fieldOffset(*index);
    *out = TypedObjectPrediction(descr.fieldDescr(*index));
    return true;
}

bool
TypedObjectPrediction::hasFieldNamed(jsid id,
                                     size_t* fieldOffset,
                                     TypedObjectPrediction* fieldType,
                                     size_t* fieldIndex) const
{
    MOZ_ASSERT(kind() == type::Struct);

    switch (predictionKind()) {
      case TypedObjectPrediction::Empty:
      case TypedObjectPrediction::Inconsistent:
        return false;

      case TypedObjectPrediction::Descr:
        return hasFieldNamedPrefix(
            descr().as<StructTypeDescr>(), ALL_FIELDS,
            id, fieldOffset, fieldType, fieldIndex);

      case TypedObjectPrediction::Prefix:
        return hasFieldNamedPrefix(
            *prefix().descr, prefix().fields,
            id, fieldOffset, fieldType, fieldIndex);

      default:
        MOZ_CRASH("Bad prediction kind");
    }
}