// Copyright (c) 2018, 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/frontend/constant_reader.h"

#include "vm/object_store.h"

namespace dart {
namespace kernel {

#define Z (zone_)
#define H (translation_helper_)

// Note: If changing how the constants are saved in the binary (and thus how
// they are read here) be aware that there's also some reading going on in
// KernelLoader::ReadVMAnnotations which then also has to be updated!

ConstantReader::ConstantReader(KernelReaderHelper* helper,
                               ActiveClass* active_class)
    : helper_(helper),
      zone_(helper->zone_),
      translation_helper_(helper->translation_helper_),
      active_class_(active_class),
      result_(Object::Handle(zone_)) {}

bool ConstantReader::IsPragmaInstanceConstant(
    intptr_t constant_index,
    intptr_t* pragma_name_constant_index,
    intptr_t* pragma_options_constant_index) {
  KernelReaderHelper reader(Z, &H, H.constants_table(), 0);
  NavigateToIndex(&reader, constant_index);

  if (reader.ReadByte() == kInstanceConstant) {
    NameIndex index = reader.ReadCanonicalNameReference();
    if (H.IsRoot(index) ||
        !H.StringEquals(H.CanonicalNameString(index), "pragma")) {
      return false;
    }
    index = H.CanonicalNameParent(index);
    if (H.IsRoot(index) ||
        !H.StringEquals(H.CanonicalNameString(index), "dart:core")) {
      return false;
    }
    const intptr_t num_type_args = reader.ReadUInt();
    if (num_type_args != 0) return false;

    const intptr_t num_fields = reader.ReadUInt();
    if (num_fields != 2) return false;

    const NameIndex field0_name = reader.ReadCanonicalNameReference();
    if (H.IsRoot(field0_name) ||
        !H.StringEquals(H.CanonicalNameString(field0_name), "name")) {
      return false;
    }
    const intptr_t name_index = reader.ReadUInt();
    if (pragma_name_constant_index != nullptr) {
      *pragma_name_constant_index = name_index;
    }

    const NameIndex field1_name = reader.ReadCanonicalNameReference();
    if (H.IsRoot(field1_name) ||
        !H.StringEquals(H.CanonicalNameString(field1_name), "options")) {
      return false;
    }
    const intptr_t options_index = reader.ReadUInt();
    if (pragma_options_constant_index != nullptr) {
      *pragma_options_constant_index = options_index;
    }
    return true;
  }
  return false;
}

bool ConstantReader::IsStringConstant(intptr_t constant_index,
                                      const char* name) {
  KernelReaderHelper reader(Z, &H, H.constants_table(), 0);
  NavigateToIndex(&reader, constant_index);

  if (reader.ReadByte() == kStringConstant) {
    const StringIndex index = reader.ReadStringReference();
    return H.StringEquals(index, name);
  }
  return false;
}

bool ConstantReader::GetStringConstant(intptr_t constant_index,
                                       String* out_value) {
  KernelReaderHelper reader(Z, &H, H.constants_table(), 0);
  NavigateToIndex(&reader, constant_index);

  if (reader.ReadByte() == kStringConstant) {
    const StringIndex index = reader.ReadStringReference();
    *out_value = H.DartSymbolPlain(index).ptr();
    return true;
  }
  return false;
}

InstancePtr ConstantReader::ReadConstantInitializer() {
  Tag tag = helper_->ReadTag();  // read tag.
  switch (tag) {
    case kSomething:
      return ReadConstantExpression();
    default:
      const auto& script = Script::Handle(Z, Script());
      H.ReportError(script, TokenPosition::kNoSource,
                    "Not a constant expression: unexpected kernel tag %s (%d)",
                    Reader::TagName(tag), tag);
  }
  return Instance::RawCast(result_.ptr());
}

InstancePtr ConstantReader::ReadConstantExpression() {
  Tag tag = helper_->ReadTag();  // read tag.
  switch (tag) {
    case kConstantExpression:
      helper_->ReadPosition();
      helper_->SkipDartType();
      result_ = ReadConstant(helper_->ReadUInt());
      break;
    case kFileUriConstantExpression:
      helper_->ReadPosition();
      helper_->ReadUInt();
      helper_->SkipDartType();
      result_ = ReadConstant(helper_->ReadUInt());
      break;
    case kInvalidExpression: {
      helper_->ReadPosition();  // Skip position.
      const String& message = H.DartString(helper_->ReadStringReference());
      const auto& script = Script::Handle(Z, Script());
      // Invalid expression message has pointer to the source code, no need to
      // report it twice.
      H.ReportError(script, TokenPosition::kNoSource, "%s",
                    message.ToCString());
      break;
    }
    default:
      const auto& script = Script::Handle(Z, Script());
      H.ReportError(script, TokenPosition::kNoSource,
                    "Not a constant expression: unexpected kernel tag %s (%d)",
                    Reader::TagName(tag), tag);
  }
  return Instance::RawCast(result_.ptr());
}

ObjectPtr ConstantReader::ReadAnnotations() {
  intptr_t list_length = helper_->ReadListLength();  // read list length.
  const auto& metadata_values =
      Array::Handle(Z, ImmutableArray::New(list_length, H.allocation_space()));
  Instance& value = Instance::Handle(Z);
  for (intptr_t i = 0; i < list_length; ++i) {
    // This will read the expression.
    value = ReadConstantExpression();
    metadata_values.SetAt(i, value);
  }
  return H.Canonicalize(metadata_values);
}

InstancePtr ConstantReader::ReadConstant(intptr_t constant_index) {
  ASSERT(!H.constants().IsNull());
  ASSERT(!H.constants_table().IsNull());  // raw bytes

  // For kernel-level cache (in contrast with script-level caching),
  // we need to access the raw constants array inside the shared
  // KernelProgramInfo directly, so that all scripts will see the
  // results after new insertions. These accesses at kernel-level
  // must be locked since mutator and background compiler can
  // access the array at the same time.
  {
    SafepointMutexLocker ml(
        H.thread()->isolate_group()->kernel_constants_mutex());
    const auto& constants_array =
        Array::Handle(Z, H.GetKernelProgramInfo().constants());
    ASSERT(constant_index < constants_array.Length());
    result_ = constants_array.At(constant_index);
  }

  // On miss, evaluate, and insert value.
  if (result_.ptr() == Object::sentinel().ptr()) {
    LeaveCompilerScope cs(H.thread());
    result_ = ReadConstantInternal(constant_index);
    SafepointMutexLocker ml(
        H.thread()->isolate_group()->kernel_constants_mutex());
    const auto& constants_array =
        Array::Handle(Z, H.GetKernelProgramInfo().constants());
    ASSERT(constant_index < constants_array.Length());
    constants_array.SetAt(constant_index, result_);
  }
  return Instance::RawCast(result_.ptr());
}

bool ConstantReader::IsInstanceConstant(intptr_t constant_index,
                                        const Class& clazz) {
  // Get reader directly into raw bytes of constant table/constant mapping.
  KernelReaderHelper reader(Z, &H, H.constants_table(), 0);
  NavigateToIndex(&reader, constant_index);

  // Peek for an instance of the given clazz.
  if (reader.ReadByte() == kInstanceConstant) {
    const NameIndex index = reader.ReadCanonicalNameReference();
    return H.LookupClassByKernelClass(index) == clazz.ptr();
  }
  return false;
}

intptr_t ConstantReader::NumConstants() {
  ASSERT(!H.constants_table().IsNull());
  KernelReaderHelper reader(Z, &H, H.constants_table(), 0);
  return NumConstants(&reader);
}

intptr_t ConstantReader::NumConstants(KernelReaderHelper* reader) {
  // Get reader directly into raw bytes of constant table/constant mapping.
  // Get the length of the constants (at the end of the mapping).
  reader->SetOffset(reader->ReaderSize() - 4);
  return reader->ReadUInt32();
}

intptr_t ConstantReader::NavigateToIndex(KernelReaderHelper* reader,
                                         intptr_t constant_index) {
  const intptr_t num_constants = NumConstants(reader);

  // Get the binary offset of the constant at the wanted index.
  reader->SetOffset(reader->ReaderSize() - 4 - (num_constants * 4) +
                    (constant_index * 4));
  const intptr_t constant_offset = reader->ReadUInt32();

  reader->SetOffset(constant_offset);

  return constant_offset;
}

InstancePtr ConstantReader::ReadConstantInternal(intptr_t constant_index) {
  // Get reader directly into raw bytes of constant table/constant mapping.
  KernelReaderHelper reader(Z, &H, H.constants_table(), 0);
  const intptr_t constant_offset = NavigateToIndex(&reader, constant_index);

  // No function types returned as part of any types built should reference
  // free parent type args, ensured by clearing the enclosing function type.
  ActiveEnclosingFunctionScope scope(active_class_, nullptr);
  // Construct constant from raw bytes.
  Instance& instance = Instance::Handle(Z);
  const intptr_t constant_tag = reader.ReadByte();
  switch (constant_tag) {
    case kNullConstant:
      instance = Instance::null();
      break;
    case kBoolConstant:
      instance = reader.ReadByte() == 1 ? Object::bool_true().ptr()
                                        : Object::bool_false().ptr();
      break;
    case kIntConstant: {
      uint8_t payload = 0;
      Tag integer_tag = reader.ReadTag(&payload);  // read tag.
      switch (integer_tag) {
        case kBigIntLiteral: {
          reader.ReadPosition();
          const String& value = H.DartString(reader.ReadStringReference());
          instance = Integer::New(value, Heap::kOld);
          break;
        }
        case kSpecializedIntLiteral: {
          reader.ReadPosition();
          const int64_t value =
              static_cast<int32_t>(payload) - SpecializedIntLiteralBias;
          instance = Integer::New(value, Heap::kOld);
          break;
        }
        case kNegativeIntLiteral: {
          reader.ReadPosition();
          const int64_t value = -static_cast<int64_t>(reader.ReadUInt());
          instance = Integer::New(value, Heap::kOld);
          break;
        }
        case kPositiveIntLiteral: {
          reader.ReadPosition();
          const int64_t value = reader.ReadUInt();
          instance = Integer::New(value, Heap::kOld);
          break;
        }
        default:
          const auto& script = Script::Handle(Z, Script());
          H.ReportError(
              script, TokenPosition::kNoSource,
              "Cannot lazily read integer: unexpected kernel tag %s (%d)",
              Reader::TagName(integer_tag), integer_tag);
      }
      break;
    }
    case kDoubleConstant:
      instance = Double::New(reader.ReadDouble(), Heap::kOld);
      break;
    case kStringConstant:
      instance = H.DartSymbolPlain(reader.ReadStringReference()).ptr();
      break;
    case kSymbolConstant: {
      Library& library = Library::Handle(Z);
      library = Library::InternalLibrary();
      const auto& symbol_class =
          Class::Handle(Z, library.LookupClass(Symbols::Symbol()));
      const auto& symbol_name_field = Field::Handle(
          Z, symbol_class.LookupInstanceFieldAllowPrivate(Symbols::_name()));
      ASSERT(!symbol_name_field.IsNull());
      const NameIndex index = reader.ReadCanonicalNameReference();
      if (index == -1) {
        library = Library::null();
      } else {
        library = H.LookupLibraryByKernelLibrary(index);
      }
      const String& symbol =
          H.DartIdentifier(library, reader.ReadStringReference());
      instance = Instance::New(symbol_class, Heap::kOld);
      instance.SetField(symbol_name_field, symbol);
      break;
    }
    case kListConstant: {
      const auto& list_class = Class::Handle(
          Z, H.isolate_group()->object_store()->immutable_array_class());
      ASSERT(!list_class.IsNull());
      ASSERT(list_class.is_finalized());
      // Build type from the raw bytes (needs temporary translator).
      TypeTranslator type_translator(&reader, this, active_class_,
                                     /* finalize = */ true,
                                     /* in_constant_context = */ true);
      auto& type_arguments =
          TypeArguments::Handle(Z, TypeArguments::New(1, Heap::kOld));
      AbstractType& type = type_translator.BuildType();
      type_arguments.SetTypeAt(0, type);
      // Instantiate class.
      type_arguments =
          list_class.GetInstanceTypeArguments(H.thread(), type_arguments);
      // Fill array with constant elements.
      const intptr_t length = reader.ReadUInt();
      const Array& array =
          Array::Handle(Z, ImmutableArray::New(length, Heap::kOld));
      array.SetTypeArguments(type_arguments);
      Instance& constant = Instance::Handle(Z);
      for (intptr_t j = 0; j < length; ++j) {
        // Recurse into lazily evaluating all "sub" constants
        // needed to evaluate the current constant.
        const intptr_t entry_index = reader.ReadUInt();
        ASSERT(entry_index < constant_offset);  // DAG!
        constant = ReadConstant(entry_index);
        array.SetAt(j, constant);
      }
      instance = array.ptr();
      break;
    }
    case kMapConstant: {
      const auto& map_class = Class::Handle(
          Z, H.isolate_group()->object_store()->const_map_impl_class());
      ASSERT(!map_class.IsNull());
      ASSERT(map_class.is_finalized());

      // Build types from the raw bytes (needs temporary translator).
      TypeTranslator type_translator(&reader, this, active_class_,
                                     /* finalize = */ true,
                                     /* in_constant_context = */ true);
      auto& type_arguments =
          TypeArguments::Handle(Z, TypeArguments::New(2, Heap::kOld));
      AbstractType& type = type_translator.BuildType();
      type_arguments.SetTypeAt(0, type);
      type = type_translator.BuildType().ptr();
      type_arguments.SetTypeAt(1, type);

      // Instantiate class.
      type_arguments =
          map_class.GetInstanceTypeArguments(H.thread(), type_arguments);

      // Fill map with constant elements.
      const auto& map = Map::Handle(Z, ConstMap::NewUninitialized(Heap::kOld));
      ASSERT_EQUAL(map.GetClassId(), kConstMapCid);
      map.SetTypeArguments(type_arguments);
      const intptr_t length = reader.ReadUInt();
      const intptr_t used_data = (length << 1);
      map.set_used_data(used_data);

      const auto& data = Array::Handle(Z, Array::New(used_data));
      map.set_data(data);

      map.set_deleted_keys(0);
      map.ComputeAndSetHashMask();

      Instance& constant = Instance::Handle(Z);
      for (intptr_t j = 0; j < used_data; ++j) {
        // Recurse into lazily evaluating all "sub" constants
        // needed to evaluate the current constant.
        const intptr_t entry_index = reader.ReadUInt();
        ASSERT(entry_index < constant_offset);  // DAG!
        constant = ReadConstant(entry_index);
        data.SetAt(j, constant);
      }

      instance = map.ptr();
      break;
    }
    case kRecordConstant: {
      const intptr_t num_positional = reader.ReadListLength();
      intptr_t num_named = 0;
      const Array* field_names = &Array::empty_array();
      {
        AlternativeReadingScope alt(&reader.reader_);
        for (intptr_t j = 0; j < num_positional; ++j) {
          reader.ReadUInt();
        }
        num_named = reader.ReadListLength();
        if (num_named > 0) {
          auto& names = Array::Handle(Z, Array::New(num_named));
          for (intptr_t j = 0; j < num_named; ++j) {
            String& name = H.DartSymbolObfuscate(reader.ReadStringReference());
            names.SetAt(j, name);
            reader.ReadUInt();
          }
          names.MakeImmutable();
          field_names = &names;
        }
      }
      const intptr_t num_fields = num_positional + num_named;
      const RecordShape shape =
          RecordShape::Register(H.thread(), num_fields, *field_names);
      const auto& record = Record::Handle(Z, Record::New(shape));
      intptr_t pos = 0;
      for (intptr_t j = 0; j < num_positional; ++j) {
        const intptr_t entry_index = reader.ReadUInt();
        ASSERT(entry_index < constant_offset);  // DAG!
        instance = ReadConstant(entry_index);
        record.SetFieldAt(pos++, instance);
      }
      reader.ReadListLength();
      for (intptr_t j = 0; j < num_named; ++j) {
        reader.ReadStringReference();
        const intptr_t entry_index = reader.ReadUInt();
        ASSERT(entry_index < constant_offset);  // DAG!
        instance = ReadConstant(entry_index);
        record.SetFieldAt(pos++, instance);
      }
      instance = record.ptr();
      break;
    }
    case kSetConstant: {
      const auto& set_class = Class::Handle(
          Z, H.isolate_group()->object_store()->const_set_impl_class());
      ASSERT(!set_class.IsNull());
      ASSERT(set_class.is_finalized());

      // Build types from the raw bytes (needs temporary translator).
      TypeTranslator type_translator(&reader, this, active_class_,
                                     /* finalize = */ true,
                                     /* in_constant_context = */ true);
      auto& type_arguments =
          TypeArguments::Handle(Z, TypeArguments::New(1, Heap::kOld));
      AbstractType& type = type_translator.BuildType();
      type_arguments.SetTypeAt(0, type);

      // Instantiate class.
      type_arguments =
          set_class.GetInstanceTypeArguments(H.thread(), type_arguments);

      // Fill set with constant elements.
      const auto& set = Set::Handle(Z, ConstSet::NewUninitialized(Heap::kOld));
      ASSERT_EQUAL(set.GetClassId(), kConstSetCid);
      set.SetTypeArguments(type_arguments);
      const intptr_t length = reader.ReadUInt();
      const intptr_t used_data = length;
      set.set_used_data(used_data);

      const auto& data = Array::Handle(Z, Array::New(used_data));
      set.set_data(data);

      set.set_deleted_keys(0);
      set.ComputeAndSetHashMask();

      Instance& constant = Instance::Handle(Z);
      for (intptr_t j = 0; j < used_data; ++j) {
        // Recurse into lazily evaluating all "sub" constants
        // needed to evaluate the current constant.
        const intptr_t entry_index = reader.ReadUInt();
        ASSERT(entry_index < constant_offset);  // DAG!
        constant = ReadConstant(entry_index);
        data.SetAt(j, constant);
      }

      instance = set.ptr();
      break;
    }
    case kInstanceConstant: {
      const NameIndex index = reader.ReadCanonicalNameReference();
      const auto& klass = Class::Handle(Z, H.LookupClassByKernelClass(index));
      if (!klass.is_declaration_loaded()) {
        FATAL(
            "Trying to evaluate an instance constant whose references class "
            "%s is not loaded yet.",
            klass.ToCString());
      }
      const auto& obj =
          Object::Handle(Z, klass.EnsureIsAllocateFinalized(H.thread()));
      ASSERT(obj.IsNull());
      ASSERT(klass.is_enum_class() || klass.is_const());
      instance = Instance::New(klass, Heap::kOld);
      // Build type from the raw bytes (needs temporary translator).
      TypeTranslator type_translator(&reader, this, active_class_,
                                     /* finalize = */ true,
                                     /* in_constant_context = */ true);
      const intptr_t number_of_type_arguments = reader.ReadUInt();
      if (klass.NumTypeArguments() > 0) {
        auto& type_arguments = TypeArguments::Handle(
            Z, TypeArguments::New(number_of_type_arguments, Heap::kOld));
        for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
          type_arguments.SetTypeAt(j, type_translator.BuildType());
        }
        // Instantiate class.
        type_arguments =
            klass.GetInstanceTypeArguments(H.thread(), type_arguments);
        instance.SetTypeArguments(type_arguments);
      } else {
        ASSERT(number_of_type_arguments == 0);
      }
      // Set the fields.
      const intptr_t number_of_fields = reader.ReadUInt();
      Field& field = Field::Handle(Z);
      Instance& constant = Instance::Handle(Z);
      for (intptr_t j = 0; j < number_of_fields; ++j) {
        field = H.LookupFieldByKernelField(reader.ReadCanonicalNameReference());
        // Recurse into lazily evaluating all "sub" constants
        // needed to evaluate the current constant.
        const intptr_t entry_index = reader.ReadUInt();
        ASSERT(entry_index < constant_offset);  // DAG!
        constant = ReadConstant(entry_index);
        instance.SetField(field, constant);
      }
      break;
    }
    case kInstantiationConstant: {
      // Recurse into lazily evaluating the "sub" constant
      // needed to evaluate the current constant.
      const intptr_t entry_index = reader.ReadUInt();
      ASSERT(entry_index < constant_offset);  // DAG!
      const auto& constant = Instance::Handle(Z, ReadConstant(entry_index));
      ASSERT(!constant.IsNull());

      // Build type from the raw bytes (needs temporary translator).
      TypeTranslator type_translator(&reader, this, active_class_,
                                     /* finalize = */ true,
                                     /* in_constant_context = */ true);
      const intptr_t number_of_type_arguments = reader.ReadUInt();
      ASSERT(number_of_type_arguments > 0);
      auto& type_arguments = TypeArguments::Handle(
          Z, TypeArguments::New(number_of_type_arguments, Heap::kOld));
      for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
        type_arguments.SetTypeAt(j, type_translator.BuildType());
      }
      type_arguments = type_arguments.Canonicalize(Thread::Current());
      // Make a copy of the old closure, and set delayed type arguments.
      Closure& closure = Closure::Handle(Z, Closure::RawCast(constant.ptr()));
      Function& function = Function::Handle(Z, closure.function());
      const auto& type_arguments2 =
          TypeArguments::Handle(Z, closure.instantiator_type_arguments());
      // The function type arguments are used for type parameters from enclosing
      // closures. Though inner closures cannot be constants. We should
      // therefore see `null here.
      ASSERT(closure.function_type_arguments() == TypeArguments::null());
      Object& context = Object::Handle(Z, closure.RawContext());
      instance = Closure::New(type_arguments2, Object::null_type_arguments(),
                              type_arguments, function, context, Heap::kOld);
      break;
    }
    case kStaticTearOffConstant:
    case kConstructorTearOffConstant:
    case kRedirectingFactoryTearOffConstant: {
      const NameIndex index = reader.ReadCanonicalNameReference();
      Function& function = Function::Handle(Z);
      if (H.IsConstructor(index)) {
        function = H.LookupConstructorByKernelConstructor(index);
      } else {
        function = H.LookupStaticMethodByKernelProcedure(index);
      }
      function = function.ImplicitClosureFunction();
      instance = function.ImplicitStaticClosure();
      break;
    }
    case kTypeLiteralConstant: {
      // Build type from the raw bytes (needs temporary translator).
      // Const canonical type erasure is not applied to constant type literals.
      // However, CFE must ensure that constant type literals can be
      // canonicalized to an identical representant independently of the null
      // safety mode currently in use (sound or unsound) or migration state of
      // the declaring library (legacy or opted-in).
      TypeTranslator type_translator(&reader, this, active_class_,
                                     /* finalize = */ true,
                                     /* in_constant_context = */ true);
      instance = type_translator.BuildType().ptr();
      break;
    }
    default:
      // We should never see unevaluated constants (kUnevaluatedConstant) in
      // the constant table, they should have been fully evaluated before we
      // get them.
      const auto& script = Script::Handle(Z, Script());
      H.ReportError(script, TokenPosition::kNoSource,
                    "Cannot lazily read constant: unexpected kernel tag (%" Pd
                    ")",
                    constant_tag);
  }
  return H.Canonicalize(instance);
}

}  // namespace kernel
}  // namespace dart
