/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "intrinsics.h"

#include "art_field-inl.h"
#include "art_method-inl.h"
#include "class_linker.h"
#include "driver/compiler_driver.h"
#include "driver/compiler_options.h"
#include "invoke_type.h"
#include "mirror/dex_cache-inl.h"
#include "nodes.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-inl.h"
#include "utils.h"

namespace art {

// Function that returns whether an intrinsic is static/direct or virtual.
static inline InvokeType GetIntrinsicInvokeType(Intrinsics i) {
  switch (i) {
    case Intrinsics::kNone:
      return kInterface;  // Non-sensical for intrinsic.
#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \
    case Intrinsics::k ## Name: \
      return IsStatic;
#include "intrinsics_list.h"
INTRINSICS_LIST(OPTIMIZING_INTRINSICS)
#undef INTRINSICS_LIST
#undef OPTIMIZING_INTRINSICS
  }
  return kInterface;
}

// Function that returns whether an intrinsic needs an environment or not.
static inline IntrinsicNeedsEnvironmentOrCache NeedsEnvironmentOrCache(Intrinsics i) {
  switch (i) {
    case Intrinsics::kNone:
      return kNeedsEnvironmentOrCache;  // Non-sensical for intrinsic.
#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \
    case Intrinsics::k ## Name: \
      return NeedsEnvironmentOrCache;
#include "intrinsics_list.h"
INTRINSICS_LIST(OPTIMIZING_INTRINSICS)
#undef INTRINSICS_LIST
#undef OPTIMIZING_INTRINSICS
  }
  return kNeedsEnvironmentOrCache;
}

// Function that returns whether an intrinsic has side effects.
static inline IntrinsicSideEffects GetSideEffects(Intrinsics i) {
  switch (i) {
    case Intrinsics::kNone:
      return kAllSideEffects;
#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \
    case Intrinsics::k ## Name: \
      return SideEffects;
#include "intrinsics_list.h"
INTRINSICS_LIST(OPTIMIZING_INTRINSICS)
#undef INTRINSICS_LIST
#undef OPTIMIZING_INTRINSICS
  }
  return kAllSideEffects;
}

// Function that returns whether an intrinsic can throw exceptions.
static inline IntrinsicExceptions GetExceptions(Intrinsics i) {
  switch (i) {
    case Intrinsics::kNone:
      return kCanThrow;
#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \
    case Intrinsics::k ## Name: \
      return Exceptions;
#include "intrinsics_list.h"
INTRINSICS_LIST(OPTIMIZING_INTRINSICS)
#undef INTRINSICS_LIST
#undef OPTIMIZING_INTRINSICS
  }
  return kCanThrow;
}

static bool CheckInvokeType(Intrinsics intrinsic, HInvoke* invoke) {
  // Whenever the intrinsic is marked as static, report an error if we find an InvokeVirtual.
  //
  // Whenever the intrinsic is marked as direct and we find an InvokeVirtual, a devirtualization
  // failure occured. We might be in a situation where we have inlined a method that calls an
  // intrinsic, but that method is in a different dex file on which we do not have a
  // verified_method that would have helped the compiler driver sharpen the call. In that case,
  // make sure that the intrinsic is actually for some final method (or in a final class), as
  // otherwise the intrinsics setup is broken.
  //
  // For the last direction, we have intrinsics for virtual functions that will perform a check
  // inline. If the precise type is known, however, the instruction will be sharpened to an
  // InvokeStaticOrDirect.
  InvokeType intrinsic_type = GetIntrinsicInvokeType(intrinsic);
  InvokeType invoke_type = invoke->GetInvokeType();
  switch (intrinsic_type) {
    case kStatic:
      return (invoke_type == kStatic);

    case kDirect:
      if (invoke_type == kDirect) {
        return true;
      }
      if (invoke_type == kVirtual) {
        ArtMethod* art_method = invoke->GetResolvedMethod();
        ScopedObjectAccess soa(Thread::Current());
        return (art_method->IsFinal() || art_method->GetDeclaringClass()->IsFinal());
      }
      return false;

    case kVirtual:
      // Call might be devirtualized.
      return (invoke_type == kVirtual || invoke_type == kDirect);

    default:
      return false;
  }
}

void IntrinsicsRecognizer::Run() {
  ScopedObjectAccess soa(Thread::Current());
  for (HBasicBlock* block : graph_->GetReversePostOrder()) {
    for (HInstructionIterator inst_it(block->GetInstructions()); !inst_it.Done();
         inst_it.Advance()) {
      HInstruction* inst = inst_it.Current();
      if (inst->IsInvoke()) {
        HInvoke* invoke = inst->AsInvoke();
        ArtMethod* art_method = invoke->GetResolvedMethod();
        if (art_method != nullptr && art_method->IsIntrinsic()) {
          Intrinsics intrinsic = static_cast<Intrinsics>(art_method->GetIntrinsic());
          if (!CheckInvokeType(intrinsic, invoke)) {
            LOG(WARNING) << "Found an intrinsic with unexpected invoke type: "
                << intrinsic << " for "
                << art_method->PrettyMethod()
                << invoke->DebugName();
          } else {
            invoke->SetIntrinsic(intrinsic,
                                 NeedsEnvironmentOrCache(intrinsic),
                                 GetSideEffects(intrinsic),
                                 GetExceptions(intrinsic));
            MaybeRecordStat(MethodCompilationStat::kIntrinsicRecognized);
          }
        }
      }
    }
  }
}

std::ostream& operator<<(std::ostream& os, const Intrinsics& intrinsic) {
  switch (intrinsic) {
    case Intrinsics::kNone:
      os << "None";
      break;
#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache, SideEffects, Exceptions, ...) \
    case Intrinsics::k ## Name: \
      os << # Name; \
      break;
#include "intrinsics_list.h"
INTRINSICS_LIST(OPTIMIZING_INTRINSICS)
#undef STATIC_INTRINSICS_LIST
#undef VIRTUAL_INTRINSICS_LIST
#undef OPTIMIZING_INTRINSICS
  }
  return os;
}

void IntrinsicVisitor::ComputeIntegerValueOfLocations(HInvoke* invoke,
                                                      CodeGenerator* codegen,
                                                      Location return_location,
                                                      Location first_argument_location) {
  if (Runtime::Current()->IsAotCompiler()) {
    if (codegen->GetCompilerOptions().IsBootImage() ||
        codegen->GetCompilerOptions().GetCompilePic()) {
      // TODO(ngeoffray): Support boot image compilation.
      return;
    }
  }

  IntegerValueOfInfo info = ComputeIntegerValueOfInfo();

  // Most common case is that we have found all we needed (classes are initialized
  // and in the boot image). Bail if not.
  if (info.integer_cache == nullptr ||
      info.integer == nullptr ||
      info.cache == nullptr ||
      info.value_offset == 0 ||
      // low and high cannot be 0, per the spec.
      info.low == 0 ||
      info.high == 0) {
    LOG(INFO) << "Integer.valueOf will not be optimized";
    return;
  }

  // The intrinsic will call if it needs to allocate a j.l.Integer.
  LocationSummary* locations = new (invoke->GetBlock()->GetGraph()->GetArena()) LocationSummary(
      invoke, LocationSummary::kCallOnMainOnly, kIntrinsified);
  if (!invoke->InputAt(0)->IsConstant()) {
    locations->SetInAt(0, Location::RequiresRegister());
  }
  locations->AddTemp(first_argument_location);
  locations->SetOut(return_location);
}

IntrinsicVisitor::IntegerValueOfInfo IntrinsicVisitor::ComputeIntegerValueOfInfo() {
  // Note that we could cache all of the data looked up here. but there's no good
  // location for it. We don't want to add it to WellKnownClasses, to avoid creating global
  // jni values. Adding it as state to the compiler singleton seems like wrong
  // separation of concerns.
  // The need for this data should be pretty rare though.

  // The most common case is that the classes are in the boot image and initialized,
  // which is easy to generate code for. We bail if not.
  Thread* self = Thread::Current();
  ScopedObjectAccess soa(self);
  Runtime* runtime = Runtime::Current();
  ClassLinker* class_linker = runtime->GetClassLinker();
  gc::Heap* heap = runtime->GetHeap();
  IntegerValueOfInfo info;
  info.integer_cache = class_linker->FindSystemClass(self, "Ljava/lang/Integer$IntegerCache;");
  if (info.integer_cache == nullptr) {
    self->ClearException();
    return info;
  }
  if (!heap->ObjectIsInBootImageSpace(info.integer_cache) || !info.integer_cache->IsInitialized()) {
    // Optimization only works if the class is initialized and in the boot image.
    return info;
  }
  info.integer = class_linker->FindSystemClass(self, "Ljava/lang/Integer;");
  if (info.integer == nullptr) {
    self->ClearException();
    return info;
  }
  if (!heap->ObjectIsInBootImageSpace(info.integer) || !info.integer->IsInitialized()) {
    // Optimization only works if the class is initialized and in the boot image.
    return info;
  }

  ArtField* field = info.integer_cache->FindDeclaredStaticField("cache", "[Ljava/lang/Integer;");
  if (field == nullptr) {
    return info;
  }
  info.cache = static_cast<mirror::ObjectArray<mirror::Object>*>(
      field->GetObject(info.integer_cache).Ptr());
  if (info.cache == nullptr) {
    return info;
  }

  if (!heap->ObjectIsInBootImageSpace(info.cache)) {
    // Optimization only works if the object is in the boot image.
    return info;
  }

  field = info.integer->FindDeclaredInstanceField("value", "I");
  if (field == nullptr) {
    return info;
  }
  info.value_offset = field->GetOffset().Int32Value();

  field = info.integer_cache->FindDeclaredStaticField("low", "I");
  if (field == nullptr) {
    return info;
  }
  info.low = field->GetInt(info.integer_cache);

  field = info.integer_cache->FindDeclaredStaticField("high", "I");
  if (field == nullptr) {
    return info;
  }
  info.high = field->GetInt(info.integer_cache);

  DCHECK_EQ(info.cache->GetLength(), info.high - info.low + 1);
  return info;
}

}  // namespace art
