// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/v8.h"

#include "src/ic/ic.h"
#include "src/ic/ic-state.h"
#include "src/objects.h"
#include "src/type-feedback-vector-inl.h"

namespace v8 {
namespace internal {

// static
TypeFeedbackVector::VectorICKind TypeFeedbackVector::FromCodeKind(
    Code::Kind kind) {
  switch (kind) {
    case Code::CALL_IC:
      return KindCallIC;
    case Code::LOAD_IC:
      return KindLoadIC;
    case Code::KEYED_LOAD_IC:
      return KindKeyedLoadIC;
    default:
      // Shouldn't get here.
      UNREACHABLE();
  }

  return KindUnused;
}


// static
Code::Kind TypeFeedbackVector::FromVectorICKind(VectorICKind kind) {
  switch (kind) {
    case KindCallIC:
      return Code::CALL_IC;
    case KindLoadIC:
      return Code::LOAD_IC;
    case KindKeyedLoadIC:
      return Code::KEYED_LOAD_IC;
    case KindUnused:
      break;
  }
  // Sentinel for no information.
  return Code::NUMBER_OF_KINDS;
}


Code::Kind TypeFeedbackVector::GetKind(FeedbackVectorICSlot slot) const {
  if (!FLAG_vector_ics) {
    // We only have CALL_ICs
    return Code::CALL_IC;
  }

  int index = VectorICComputer::index(kReservedIndexCount, slot.ToInt());
  int data = Smi::cast(get(index))->value();
  VectorICKind b = VectorICComputer::decode(data, slot.ToInt());
  return FromVectorICKind(b);
}


void TypeFeedbackVector::SetKind(FeedbackVectorICSlot slot, Code::Kind kind) {
  if (!FLAG_vector_ics) {
    // Nothing to do if we only have CALL_ICs
    return;
  }

  VectorICKind b = FromCodeKind(kind);
  int index = VectorICComputer::index(kReservedIndexCount, slot.ToInt());
  int data = Smi::cast(get(index))->value();
  int new_data = VectorICComputer::encode(data, slot.ToInt(), b);
  set(index, Smi::FromInt(new_data));
}


// static
Handle<TypeFeedbackVector> TypeFeedbackVector::Allocate(
    Isolate* isolate, const FeedbackVectorSpec& spec) {
  const int slot_count = spec.slots();
  const int ic_slot_count = spec.ic_slots();
  const int index_count =
      FLAG_vector_ics ? VectorICComputer::word_count(ic_slot_count) : 0;
  const int length =
      slot_count + ic_slot_count + index_count + kReservedIndexCount;
  if (length == kReservedIndexCount) {
    return Handle<TypeFeedbackVector>::cast(
        isolate->factory()->empty_fixed_array());
  }

  Handle<FixedArray> array = isolate->factory()->NewFixedArray(length, TENURED);
  if (ic_slot_count > 0) {
    array->set(kFirstICSlotIndex,
               Smi::FromInt(slot_count + index_count + kReservedIndexCount));
  } else {
    array->set(kFirstICSlotIndex, Smi::FromInt(length));
  }
  array->set(kWithTypesIndex, Smi::FromInt(0));
  array->set(kGenericCountIndex, Smi::FromInt(0));
  // Fill the indexes with zeros.
  for (int i = 0; i < index_count; i++) {
    array->set(kReservedIndexCount + i, Smi::FromInt(0));
  }

  // Ensure we can skip the write barrier
  Handle<Object> uninitialized_sentinel = UninitializedSentinel(isolate);
  DCHECK_EQ(isolate->heap()->uninitialized_symbol(), *uninitialized_sentinel);
  for (int i = kReservedIndexCount + index_count; i < length; i++) {
    array->set(i, *uninitialized_sentinel, SKIP_WRITE_BARRIER);
  }

  Handle<TypeFeedbackVector> vector = Handle<TypeFeedbackVector>::cast(array);
  if (FLAG_vector_ics) {
    for (int i = 0; i < ic_slot_count; i++) {
      vector->SetKind(FeedbackVectorICSlot(i), spec.GetKind(i));
    }
  }
  return vector;
}


// static
Handle<TypeFeedbackVector> TypeFeedbackVector::Copy(
    Isolate* isolate, Handle<TypeFeedbackVector> vector) {
  Handle<TypeFeedbackVector> result;
  result = Handle<TypeFeedbackVector>::cast(
      isolate->factory()->CopyFixedArray(Handle<FixedArray>::cast(vector)));
  return result;
}


// This logic is copied from
// StaticMarkingVisitor<StaticVisitor>::VisitCodeTarget.
// TODO(mvstanton): with weak handling of all vector ics, this logic should
// actually be completely eliminated and we no longer need to clear the
// vector ICs.
static bool ClearLogic(Heap* heap, int ic_age, Code::Kind kind,
                       InlineCacheState state) {
  if (FLAG_cleanup_code_caches_at_gc &&
      (kind == Code::CALL_IC || heap->flush_monomorphic_ics() ||
       // TODO(mvstanton): is this ic_age granular enough? it comes from
       // the SharedFunctionInfo which may change on a different schedule
       // than ic targets.
       // ic_age != heap->global_ic_age() ||
       // is_invalidated_weak_stub ||
       heap->isolate()->serializer_enabled())) {
    return true;
  }
  return false;
}


void TypeFeedbackVector::ClearSlots(SharedFunctionInfo* shared) {
  int slots = Slots();
  Isolate* isolate = GetIsolate();
  Object* uninitialized_sentinel =
      TypeFeedbackVector::RawUninitializedSentinel(isolate->heap());

  for (int i = 0; i < slots; i++) {
    FeedbackVectorSlot slot(i);
    Object* obj = Get(slot);
    if (obj->IsHeapObject()) {
      InstanceType instance_type =
          HeapObject::cast(obj)->map()->instance_type();
      // AllocationSites are exempt from clearing. They don't store Maps
      // or Code pointers which can cause memory leaks if not cleared
      // regularly.
      if (instance_type != ALLOCATION_SITE_TYPE) {
        Set(slot, uninitialized_sentinel, SKIP_WRITE_BARRIER);
      }
    }
  }

  slots = ICSlots();
  if (slots == 0) return;

  // Now clear vector-based ICs.
  // Try and pass the containing code (the "host").
  Heap* heap = isolate->heap();
  Code* host = shared->code();
  // I'm not sure yet if this ic age is the correct one.
  int ic_age = shared->ic_age();
  for (int i = 0; i < slots; i++) {
    FeedbackVectorICSlot slot(i);
    Object* obj = Get(slot);
    if (obj != uninitialized_sentinel) {
      Code::Kind kind = GetKind(slot);
      if (kind == Code::CALL_IC) {
        CallICNexus nexus(this, slot);
        if (ClearLogic(heap, ic_age, kind, nexus.StateFromFeedback())) {
          nexus.Clear(host);
        }
      } else if (kind == Code::LOAD_IC) {
        LoadICNexus nexus(this, slot);
        if (ClearLogic(heap, ic_age, kind, nexus.StateFromFeedback())) {
          nexus.Clear(host);
        }
      } else if (kind == Code::KEYED_LOAD_IC) {
        KeyedLoadICNexus nexus(this, slot);
        if (ClearLogic(heap, ic_age, kind, nexus.StateFromFeedback())) {
          nexus.Clear(host);
        }
      }
    }
  }
}


Handle<FixedArray> FeedbackNexus::EnsureArrayOfSize(int length) {
  Isolate* isolate = GetIsolate();
  Handle<Object> feedback = handle(GetFeedback(), isolate);
  if (!feedback->IsFixedArray() ||
      FixedArray::cast(*feedback)->length() != length) {
    Handle<FixedArray> array = isolate->factory()->NewFixedArray(length);
    SetFeedback(*array);
    return array;
  }
  return Handle<FixedArray>::cast(feedback);
}


void FeedbackNexus::InstallHandlers(int start_index, TypeHandleList* types,
                                    CodeHandleList* handlers) {
  Isolate* isolate = GetIsolate();
  Handle<FixedArray> array = handle(FixedArray::cast(GetFeedback()), isolate);
  int receiver_count = types->length();
  for (int current = 0; current < receiver_count; ++current) {
    Handle<HeapType> type = types->at(current);
    Handle<Map> map = IC::TypeToMap(*type, isolate);
    Handle<WeakCell> cell = Map::WeakCellForMap(map);
    array->set(start_index + (current * 2), *cell);
    array->set(start_index + (current * 2 + 1), *handlers->at(current));
  }
}


InlineCacheState LoadICNexus::StateFromFeedback() const {
  Isolate* isolate = GetIsolate();
  Object* feedback = GetFeedback();
  if (feedback == *vector()->UninitializedSentinel(isolate)) {
    return UNINITIALIZED;
  } else if (feedback == *vector()->MegamorphicSentinel(isolate)) {
    return MEGAMORPHIC;
  } else if (feedback == *vector()->PremonomorphicSentinel(isolate)) {
    return PREMONOMORPHIC;
  } else if (feedback->IsFixedArray()) {
    // Determine state purely by our structure, don't check if the maps are
    // cleared.
    FixedArray* array = FixedArray::cast(feedback);
    int length = array->length();
    DCHECK(length >= 2);
    return length == 2 ? MONOMORPHIC : POLYMORPHIC;
  }

  return UNINITIALIZED;
}


InlineCacheState KeyedLoadICNexus::StateFromFeedback() const {
  Isolate* isolate = GetIsolate();
  Object* feedback = GetFeedback();
  if (feedback == *vector()->UninitializedSentinel(isolate)) {
    return UNINITIALIZED;
  } else if (feedback == *vector()->PremonomorphicSentinel(isolate)) {
    return PREMONOMORPHIC;
  } else if (feedback == *vector()->GenericSentinel(isolate)) {
    return GENERIC;
  } else if (feedback->IsFixedArray()) {
    // Determine state purely by our structure, don't check if the maps are
    // cleared.
    FixedArray* array = FixedArray::cast(feedback);
    int length = array->length();
    DCHECK(length >= 3);
    return length == 3 ? MONOMORPHIC : POLYMORPHIC;
  }

  return UNINITIALIZED;
}


InlineCacheState CallICNexus::StateFromFeedback() const {
  Isolate* isolate = GetIsolate();
  Object* feedback = GetFeedback();

  if (feedback == *vector()->MegamorphicSentinel(isolate)) {
    return GENERIC;
  } else if (feedback->IsAllocationSite() || feedback->IsJSFunction()) {
    return MONOMORPHIC;
  }

  CHECK(feedback == *vector()->UninitializedSentinel(isolate));
  return UNINITIALIZED;
}


void CallICNexus::Clear(Code* host) { CallIC::Clear(GetIsolate(), host, this); }


void CallICNexus::ConfigureGeneric() {
  SetFeedback(*vector()->MegamorphicSentinel(GetIsolate()), SKIP_WRITE_BARRIER);
}


void CallICNexus::ConfigureMonomorphicArray() {
  Object* feedback = GetFeedback();
  if (!feedback->IsAllocationSite()) {
    Handle<AllocationSite> new_site =
        GetIsolate()->factory()->NewAllocationSite();
    SetFeedback(*new_site);
  }
}


void CallICNexus::ConfigureUninitialized() {
  SetFeedback(*vector()->UninitializedSentinel(GetIsolate()),
              SKIP_WRITE_BARRIER);
}


void CallICNexus::ConfigureMonomorphic(Handle<JSFunction> function) {
  SetFeedback(*function);
}


void KeyedLoadICNexus::ConfigureGeneric() {
  SetFeedback(*vector()->GenericSentinel(GetIsolate()), SKIP_WRITE_BARRIER);
}


void LoadICNexus::ConfigureMegamorphic() {
  SetFeedback(*vector()->MegamorphicSentinel(GetIsolate()), SKIP_WRITE_BARRIER);
}


void LoadICNexus::ConfigurePremonomorphic() {
  SetFeedback(*vector()->PremonomorphicSentinel(GetIsolate()),
              SKIP_WRITE_BARRIER);
}


void KeyedLoadICNexus::ConfigurePremonomorphic() {
  SetFeedback(*vector()->PremonomorphicSentinel(GetIsolate()),
              SKIP_WRITE_BARRIER);
}


void LoadICNexus::ConfigureMonomorphic(Handle<HeapType> type,
                                       Handle<Code> handler) {
  Handle<FixedArray> array = EnsureArrayOfSize(2);
  Handle<Map> receiver_map = IC::TypeToMap(*type, GetIsolate());
  Handle<WeakCell> cell = Map::WeakCellForMap(receiver_map);
  array->set(0, *cell);
  array->set(1, *handler);
}


void KeyedLoadICNexus::ConfigureMonomorphic(Handle<Name> name,
                                            Handle<HeapType> type,
                                            Handle<Code> handler) {
  Handle<FixedArray> array = EnsureArrayOfSize(3);
  Handle<Map> receiver_map = IC::TypeToMap(*type, GetIsolate());
  if (name.is_null()) {
    array->set(0, Smi::FromInt(0));
  } else {
    array->set(0, *name);
  }
  Handle<WeakCell> cell = Map::WeakCellForMap(receiver_map);
  array->set(1, *cell);
  array->set(2, *handler);
}


void LoadICNexus::ConfigurePolymorphic(TypeHandleList* types,
                                       CodeHandleList* handlers) {
  int receiver_count = types->length();
  EnsureArrayOfSize(receiver_count * 2);
  InstallHandlers(0, types, handlers);
}


void KeyedLoadICNexus::ConfigurePolymorphic(Handle<Name> name,
                                            TypeHandleList* types,
                                            CodeHandleList* handlers) {
  int receiver_count = types->length();
  Handle<FixedArray> array = EnsureArrayOfSize(1 + receiver_count * 2);
  if (name.is_null()) {
    array->set(0, Smi::FromInt(0));
  } else {
    array->set(0, *name);
  }
  InstallHandlers(1, types, handlers);
}


int FeedbackNexus::ExtractMaps(int start_index, MapHandleList* maps) const {
  Isolate* isolate = GetIsolate();
  Object* feedback = GetFeedback();
  if (feedback->IsFixedArray()) {
    int found = 0;
    FixedArray* array = FixedArray::cast(feedback);
    // The array should be of the form [<optional name>], then
    // [map, handler, map, handler, ... ]
    DCHECK(array->length() >= (2 + start_index));
    for (int i = start_index; i < array->length(); i += 2) {
      WeakCell* cell = WeakCell::cast(array->get(i));
      if (!cell->cleared()) {
        Map* map = Map::cast(cell->value());
        maps->Add(handle(map, isolate));
        found++;
      }
    }
    return found;
  }

  return 0;
}


MaybeHandle<Code> FeedbackNexus::FindHandlerForMap(int start_index,
                                                   Handle<Map> map) const {
  Object* feedback = GetFeedback();
  if (feedback->IsFixedArray()) {
    FixedArray* array = FixedArray::cast(feedback);
    for (int i = start_index; i < array->length(); i += 2) {
      WeakCell* cell = WeakCell::cast(array->get(i));
      if (!cell->cleared()) {
        Map* array_map = Map::cast(cell->value());
        if (array_map == *map) {
          Code* code = Code::cast(array->get(i + 1));
          DCHECK(code->kind() == Code::HANDLER);
          return handle(code);
        }
      }
    }
  }

  return MaybeHandle<Code>();
}


bool FeedbackNexus::FindHandlers(int start_index, CodeHandleList* code_list,
                                 int length) const {
  Object* feedback = GetFeedback();
  int count = 0;
  if (feedback->IsFixedArray()) {
    FixedArray* array = FixedArray::cast(feedback);
    // The array should be of the form [<optional name>], then
    // [map, handler, map, handler, ... ]. Be sure to skip handlers whose maps
    // have been cleared.
    DCHECK(array->length() >= (2 + start_index));
    for (int i = start_index; i < array->length(); i += 2) {
      WeakCell* cell = WeakCell::cast(array->get(i));
      if (!cell->cleared()) {
        Code* code = Code::cast(array->get(i + 1));
        DCHECK(code->kind() == Code::HANDLER);
        code_list->Add(handle(code));
        count++;
      }
    }
  }
  return count == length;
}


int LoadICNexus::ExtractMaps(MapHandleList* maps) const {
  return FeedbackNexus::ExtractMaps(0, maps);
}


void LoadICNexus::Clear(Code* host) { LoadIC::Clear(GetIsolate(), host, this); }


void KeyedLoadICNexus::Clear(Code* host) {
  KeyedLoadIC::Clear(GetIsolate(), host, this);
}


int KeyedLoadICNexus::ExtractMaps(MapHandleList* maps) const {
  return FeedbackNexus::ExtractMaps(1, maps);
}


MaybeHandle<Code> LoadICNexus::FindHandlerForMap(Handle<Map> map) const {
  return FeedbackNexus::FindHandlerForMap(0, map);
}


MaybeHandle<Code> KeyedLoadICNexus::FindHandlerForMap(Handle<Map> map) const {
  return FeedbackNexus::FindHandlerForMap(1, map);
}


bool LoadICNexus::FindHandlers(CodeHandleList* code_list, int length) const {
  return FeedbackNexus::FindHandlers(0, code_list, length);
}


bool KeyedLoadICNexus::FindHandlers(CodeHandleList* code_list,
                                    int length) const {
  return FeedbackNexus::FindHandlers(1, code_list, length);
}


Name* KeyedLoadICNexus::FindFirstName() const {
  Object* feedback = GetFeedback();
  if (feedback->IsFixedArray()) {
    FixedArray* array = FixedArray::cast(feedback);
    DCHECK(array->length() >= 3);
    Object* name = array->get(0);
    if (name->IsName()) return Name::cast(name);
  }
  return NULL;
}
}
}  // namespace v8::internal
