// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/compiler/cha.h"
#include "vm/class_table.h"
#include "vm/compiler/compiler_state.h"
#include "vm/flags.h"
#include "vm/log.h"
#include "vm/object.h"
#include "vm/raw_object.h"
#include "vm/visitor.h"

namespace dart {

void CHA::AddToGuardedClasses(const Class& cls,
                              intptr_t subclass_count,
                              intptr_t implementor_cid,
                              bool track_future) {
  for (intptr_t i = 0; i < guarded_classes_.length(); i++) {
    if (guarded_classes_[i].cls->ptr() == cls.ptr()) {
      if ((subclass_count >= 0) && (guarded_classes_[i].subclass_count == -1)) {
        guarded_classes_[i].subclass_count = subclass_count;
      }
      if ((implementor_cid != kIllegalCid) &&
          (guarded_classes_[i].implementor_cid == kIllegalCid)) {
        guarded_classes_[i].implementor_cid = implementor_cid;
      }
      if (track_future && !guarded_classes_[i].track_future) {
        guarded_classes_[i].track_future = track_future;
      }
      return;
    }
  }
  GuardedClassInfo info = {&Class::ZoneHandle(thread_->zone(), cls.ptr()),
                           subclass_count, implementor_cid, track_future};
  guarded_classes_.Add(info);
}

void CHA::AddToGuardedClassesForSubclassCount(const Class& cls,
                                              intptr_t subclass_count) {
  ASSERT(subclass_count >= 0);
  AddToGuardedClasses(cls, subclass_count, kIllegalCid, false);
}

void CHA::AddToGuardedClassesForImplementorCid(const Class& cls,
                                               intptr_t implementor_cid) {
  ASSERT(implementor_cid != kIllegalCid);
  ASSERT(implementor_cid != kDynamicCid);
  AddToGuardedClasses(cls, -1, implementor_cid, false);
}

void CHA::AddToGuardedClassesToTrackFuture(const Class& cls) {
  AddToGuardedClasses(cls, -1, kIllegalCid, true);
}

bool CHA::IsGuardedClass(intptr_t cid) const {
  for (intptr_t i = 0; i < guarded_classes_.length(); ++i) {
    if (guarded_classes_[i].cls->id() == cid) return true;
  }
  return false;
}

bool CHA::HasSubclasses(const Class& cls) {
  ASSERT(!cls.IsNull());
  ASSERT(!IsInternalOnlyClassId(cls.id()));

  if (cls.IsObjectClass()) {
    // Class Object has subclasses, although we do not keep track of them.
    return true;
  }

  if (cls.has_dynamically_extendable_subtypes()) return true;

  Thread* thread = Thread::Current();
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  const GrowableObjectArray& direct_subclasses =
      GrowableObjectArray::Handle(cls.direct_subclasses());
  return !direct_subclasses.IsNull() && (direct_subclasses.Length() > 0);
}

bool CHA::HasSubclasses(intptr_t cid) const {
  const ClassTable& class_table = *thread_->isolate_group()->class_table();
  Class& cls = Class::Handle(thread_->zone(), class_table.At(cid));
  return HasSubclasses(cls);
}

bool CHA::ConcreteSubclasses(const Class& cls,
                             GrowableArray<intptr_t>* class_ids) {
  if (cls.IsObjectClass()) return false;
  if (cls.has_dynamically_extendable_subtypes()) return false;

  if (!cls.is_abstract()) {
    class_ids->Add(cls.id());
  }

  // This is invoked from precompiler only, we can use unsafe version of
  // Class::direct_subclasses getter.
  ASSERT(FLAG_precompiled_mode);
  const GrowableObjectArray& direct_subclasses =
      GrowableObjectArray::Handle(cls.direct_subclasses_unsafe());
  if (direct_subclasses.IsNull()) {
    return true;
  }
  Class& subclass = Class::Handle();
  for (intptr_t i = 0; i < direct_subclasses.Length(); i++) {
    subclass ^= direct_subclasses.At(i);
    if (!ConcreteSubclasses(subclass, class_ids)) {
      return false;
    }
  }
  return true;
}

bool CHA::IsImplemented(const Class& cls) {
  if (cls.has_dynamically_extendable_subtypes()) return true;

  return cls.is_implemented();
}

bool CHA::HasSingleConcreteImplementation(const Class& interface,
                                          intptr_t* implementation_cid) {
  intptr_t cid = interface.implementor_cid();
  if ((cid == kIllegalCid) || (cid == kDynamicCid) ||
      interface.has_dynamically_extendable_subtypes()) {
    // No implementations / multiple implementations.
    *implementation_cid = kDynamicCid;
    return false;
  }

  Thread* thread = Thread::Current();
  if (FLAG_use_cha_deopt || thread->isolate_group()->all_classes_finalized()) {
    if (FLAG_trace_cha) {
      THR_Print("  **(CHA) Type has one implementation: %s\n",
                interface.ToCString());
    }
    if (FLAG_use_cha_deopt) {
      CHA& cha = thread->compiler_state().cha();
      cha.AddToGuardedClassesForImplementorCid(interface, cid);
    }
    *implementation_cid = cid;
    return true;
  } else {
    *implementation_cid = kDynamicCid;
    return false;
  }
}

bool CHA::ClassCanBeFuture(const Class& cls) {
  if (cls.can_be_future()) {
    return true;
  }

  // Class cannot be Future with the current set of
  // finalized classes. However, as new classes are loaded
  // and finalized, there could be a new subtype of [cls]
  // which is also a subtype of Future.
  // We should deoptimize in such cases.
  Thread* thread = Thread::Current();
  if (FLAG_use_cha_deopt || thread->isolate_group()->all_classes_finalized()) {
    if (FLAG_use_cha_deopt) {
      CHA& cha = thread->compiler_state().cha();
      cha.AddToGuardedClassesToTrackFuture(cls);
    }
    return false;
  }

  // Conservatively assume that class can be Future.
  return true;
}

static intptr_t CountFinalizedSubclasses(Thread* thread, const Class& cls) {
  intptr_t count = 0;
  const GrowableObjectArray& cls_direct_subclasses =
      GrowableObjectArray::Handle(thread->zone(), cls.direct_subclasses());
  if (cls_direct_subclasses.IsNull()) return count;
  Class& direct_subclass = Class::Handle(thread->zone());
  for (intptr_t i = 0; i < cls_direct_subclasses.Length(); i++) {
    direct_subclass ^= cls_direct_subclasses.At(i);
    // Unfinalized classes are treated as nonexistent for CHA purposes,
    // as that means that no instance of that class exists at runtime.
    if (!direct_subclass.is_finalized()) {
      continue;
    }

    count += 1 + CountFinalizedSubclasses(thread, direct_subclass);
  }
  return count;
}

bool CHA::IsConsistentWithCurrentHierarchy() const {
  for (intptr_t i = 0; i < guarded_classes_.length(); i++) {
    if (guarded_classes_[i].subclass_count != -1) {
      intptr_t current_subclass_count =
          CountFinalizedSubclasses(thread_, *guarded_classes_[i].cls);
      if (guarded_classes_[i].subclass_count != current_subclass_count) {
        return false;  // New subclass appeared during compilation.
      }
    }
    if (guarded_classes_[i].implementor_cid != kIllegalCid) {
      intptr_t current_implementor_cid =
          guarded_classes_[i].cls->implementor_cid();
      if (guarded_classes_[i].implementor_cid != current_implementor_cid) {
        return false;  // New implementor appeared during compilation.
      }
    }
    if (guarded_classes_[i].track_future) {
      if (guarded_classes_[i].cls->can_be_future()) {
        return false;
      }
    }
  }
  return true;
}

bool CHA::HasOverride(const Class& cls,
                      const String& function_name,
                      intptr_t* subclasses_count) {
  // Subclasses of Object are not tracked by CHA. Safely assume that overrides
  // exist.
  if (cls.IsObjectClass()) {
    return true;
  }

  SafepointReadRwLocker ml(thread_, thread_->isolate_group()->program_lock());
  const GrowableObjectArray& cls_direct_subclasses =
      GrowableObjectArray::Handle(thread_->zone(), cls.direct_subclasses());
  if (cls_direct_subclasses.IsNull()) {
    return false;
  }
  Class& direct_subclass = Class::Handle(thread_->zone());
  for (intptr_t i = 0; i < cls_direct_subclasses.Length(); i++) {
    direct_subclass ^= cls_direct_subclasses.At(i);
    // Unfinalized classes are treated as nonexistent for CHA purposes,
    // as that means that no instance of that class exists at runtime.
    if (!direct_subclass.is_finalized()) {
      continue;
    }

    if (direct_subclass.LookupDynamicFunctionUnsafe(function_name) !=
        Function::null()) {
      return true;
    }

    if (HasOverride(direct_subclass, function_name, subclasses_count)) {
      return true;
    }

    (*subclasses_count)++;
  }

  return false;
}

void CHA::RegisterDependencies(const Code& code) const {
  for (intptr_t i = 0; i < guarded_classes_.length(); ++i) {
    guarded_classes_[i].cls->RegisterCHACode(code);
  }
}

}  // namespace dart
