/* -*- 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");
  }
}
