// Copyright 2017 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/torque/declaration-visitor.h"

namespace v8 {
namespace internal {
namespace torque {

void DeclarationVisitor::Visit(Expression* expr) {
  CurrentSourcePosition::Scope scope(expr->pos);
  switch (expr->kind) {
#define ENUM_ITEM(name)        \
  case AstNode::Kind::k##name: \
    return Visit(name::cast(expr));
    AST_EXPRESSION_NODE_KIND_LIST(ENUM_ITEM)
#undef ENUM_ITEM
    default:
      UNIMPLEMENTED();
  }
}

void DeclarationVisitor::Visit(Statement* stmt) {
  CurrentSourcePosition::Scope scope(stmt->pos);
  switch (stmt->kind) {
#define ENUM_ITEM(name)        \
  case AstNode::Kind::k##name: \
    return Visit(name::cast(stmt));
    AST_STATEMENT_NODE_KIND_LIST(ENUM_ITEM)
#undef ENUM_ITEM
    default:
      UNIMPLEMENTED();
  }
}

void DeclarationVisitor::Visit(Declaration* decl) {
  CurrentSourcePosition::Scope scope(decl->pos);
  switch (decl->kind) {
#define ENUM_ITEM(name)        \
  case AstNode::Kind::k##name: \
    return Visit(name::cast(decl));
    AST_DECLARATION_NODE_KIND_LIST(ENUM_ITEM)
#undef ENUM_ITEM
    default:
      UNIMPLEMENTED();
  }
}

void DeclarationVisitor::Visit(CallableNode* decl, const Signature& signature,
                               Statement* body) {
  switch (decl->kind) {
#define ENUM_ITEM(name)        \
  case AstNode::Kind::k##name: \
    return Visit(name::cast(decl), signature, body);
    AST_CALLABLE_NODE_KIND_LIST(ENUM_ITEM)
#undef ENUM_ITEM
    default:
      UNIMPLEMENTED();
  }
}

Builtin* DeclarationVisitor::BuiltinDeclarationCommon(
    BuiltinDeclaration* decl, bool external, const Signature& signature) {
  const bool javascript = decl->javascript_linkage;
  const bool varargs = decl->signature->parameters.has_varargs;
  Builtin::Kind kind = !javascript ? Builtin::kStub
                                   : varargs ? Builtin::kVarArgsJavaScript
                                             : Builtin::kFixedArgsJavaScript;

  if (signature.types().size() == 0 ||
      !(signature.types()[0] ==
        declarations()->LookupGlobalType(CONTEXT_TYPE_STRING))) {
    std::stringstream stream;
    stream << "first parameter to builtin " << decl->name
           << " is not a context but should be";
    ReportError(stream.str());
  }

  if (varargs && !javascript) {
    std::stringstream stream;
    stream << "builtin " << decl->name
           << " with rest parameters must be a JavaScript builtin";
    ReportError(stream.str());
  }

  if (javascript) {
    if (signature.types().size() < 2 ||
        !(signature.types()[1] ==
          declarations()->LookupGlobalType(OBJECT_TYPE_STRING))) {
      std::stringstream stream;
      stream << "second parameter to javascript builtin " << decl->name
             << " is " << *signature.types()[1] << " but should be Object";
      ReportError(stream.str());
    }
  }

  if (const StructType* struct_type =
          StructType::DynamicCast(signature.return_type)) {
    std::stringstream stream;
    stream << "builtins (in this case" << decl->name
           << ") cannot return structs (in this case " << struct_type->name()
           << ")";
    ReportError(stream.str());
  }

  std::string generated_name = GetGeneratedCallableName(
      decl->name, declarations()->GetCurrentSpecializationTypeNamesVector());
  return declarations()->DeclareBuiltin(generated_name, kind, external,
                                        signature);
}

void DeclarationVisitor::Visit(ExternalRuntimeDeclaration* decl,
                               const Signature& signature, Statement* body) {
  if (global_context_.verbose()) {
    std::cout << "found declaration of external runtime " << decl->name
              << " with signature ";
  }

  if (signature.parameter_types.types.size() == 0 ||
      !(signature.parameter_types.types[0] ==
        declarations()->LookupGlobalType(CONTEXT_TYPE_STRING))) {
    std::stringstream stream;
    stream << "first parameter to runtime " << decl->name
           << " is not a context but should be";
    ReportError(stream.str());
  }

  if (signature.return_type->IsStructType()) {
    std::stringstream stream;
    stream << "runtime functions (in this case" << decl->name
           << ") cannot return structs (in this case "
           << static_cast<const StructType*>(signature.return_type)->name()
           << ")";
    ReportError(stream.str());
  }

  declarations()->DeclareRuntimeFunction(decl->name, signature);
}

void DeclarationVisitor::Visit(ExternalMacroDeclaration* decl,
                               const Signature& signature, Statement* body) {
  if (global_context_.verbose()) {
    std::cout << "found declaration of external macro " << decl->name
              << " with signature ";
  }

  std::string generated_name = GetGeneratedCallableName(
      decl->name, declarations()->GetCurrentSpecializationTypeNamesVector());
  declarations()->DeclareMacro(generated_name, signature, decl->op);
}

void DeclarationVisitor::Visit(TorqueBuiltinDeclaration* decl,
                               const Signature& signature, Statement* body) {
  Builtin* builtin = BuiltinDeclarationCommon(decl, false, signature);
  CurrentCallableActivator activator(global_context_, builtin, decl);
  DeclareSignature(signature);
  if (signature.parameter_types.var_args) {
    declarations()->DeclareExternConstant(
        decl->signature->parameters.arguments_variable,
        TypeOracle::GetArgumentsType(), "arguments");
  }
  torque_builtins_.push_back(builtin);
  Visit(body);
}

void DeclarationVisitor::Visit(TorqueMacroDeclaration* decl,
                               const Signature& signature, Statement* body) {
  std::string generated_name = GetGeneratedCallableName(
      decl->name, declarations()->GetCurrentSpecializationTypeNamesVector());
  Macro* macro =
      declarations()->DeclareMacro(generated_name, signature, decl->op);

  CurrentCallableActivator activator(global_context_, macro, decl);

  DeclareSignature(signature);
  Variable* return_variable = nullptr;
  if (!signature.return_type->IsVoidOrNever()) {
    return_variable =
        DeclareVariable(kReturnValueVariable, signature.return_type,
                        signature.return_type->IsConstexpr());
  }

  PushControlSplit();
  if (body != nullptr) {
    Visit(body);
  }
  auto changed_vars = PopControlSplit();
  if (return_variable) changed_vars.insert(return_variable);
  global_context_.AddControlSplitChangedVariables(
      decl, declarations()->GetCurrentSpecializationTypeNamesVector(),
      changed_vars);
}

void DeclarationVisitor::Visit(ConstDeclaration* decl) {
  declarations()->DeclareModuleConstant(decl->name,
                                        declarations()->GetType(decl->type));
  Visit(decl->expression);
}

void DeclarationVisitor::Visit(StandardDeclaration* decl) {
  Signature signature = MakeSignature(decl->callable->signature.get());
  Visit(decl->callable, signature, decl->body);
}

void DeclarationVisitor::Visit(GenericDeclaration* decl) {
  declarations()->DeclareGeneric(decl->callable->name, CurrentModule(), decl);
}

void DeclarationVisitor::Visit(SpecializationDeclaration* decl) {
  if ((decl->body != nullptr) == decl->external) {
    std::stringstream stream;
    stream << "specialization of " << decl->name
           << " must either be marked 'extern' or have a body";
    ReportError(stream.str());
  }

  GenericList* generic_list = declarations()->LookupGeneric(decl->name);
  // Find the matching generic specialization based on the concrete parameter
  // list.
  CallableNode* matching_callable = nullptr;
  SpecializationKey matching_key;
  Signature signature_with_types = MakeSignature(decl->signature.get());
  for (Generic* generic : generic_list->list()) {
    SpecializationKey key = {generic, GetTypeVector(decl->generic_parameters)};
    CallableNode* callable_candidate = generic->declaration()->callable;
    // Abuse the Specialization nodes' scope to temporarily declare the
    // specialization aliases for the generic types to compare signatures. This
    // scope is never used for anything else, so it's OK to pollute it.
    Declarations::CleanNodeScopeActivator specialization_activator(
        declarations(), decl);
    DeclareSpecializedTypes(key);
    Signature generic_signature_with_types =
        MakeSignature(generic->declaration()->callable->signature.get());
    if (signature_with_types.HasSameTypesAs(generic_signature_with_types)) {
      if (matching_callable != nullptr) {
        std::stringstream stream;
        stream << "specialization of " << callable_candidate->name
               << " is ambigous, it matches more than one generic declaration ("
               << *matching_key.first << " and " << *key.first << ")";
        ReportError(stream.str());
      }
      matching_callable = callable_candidate;
      matching_key = key;
    }
  }

  if (matching_callable == nullptr) {
    std::stringstream stream;
    stream << "specialization of " << decl->name
           << " doesn't match any generic declaration";
    ReportError(stream.str());
  }

  // Make sure the declarations of the parameter types for the specialization
  // are the ones from the matching generic.
  {
    Declarations::CleanNodeScopeActivator specialization_activator(
        declarations(), decl);
    DeclareSpecializedTypes(matching_key);
  }

  SpecializeGeneric({matching_key, matching_callable, decl->signature.get(),
                     decl->body, decl->pos});
}

void DeclarationVisitor::Visit(ReturnStatement* stmt) {
  if (stmt->value) {
    Visit(*stmt->value);
  }
}

Variable* DeclarationVisitor::DeclareVariable(const std::string& name,
                                              const Type* type, bool is_const) {
  Variable* result = declarations()->DeclareVariable(name, type, is_const);
  if (type->IsStructType()) {
    const StructType* struct_type = StructType::cast(type);
    for (auto& field : struct_type->fields()) {
      std::string field_var_name = name + "." + field.name;
      DeclareVariable(field_var_name, field.type, is_const);
    }
  }
  return result;
}

Parameter* DeclarationVisitor::DeclareParameter(const std::string& name,
                                                const Type* type) {
  Parameter* result = declarations()->DeclareParameter(
      name, GetParameterVariableFromName(name), type);
  if (type->IsStructType()) {
    const StructType* struct_type = StructType::cast(type);
    for (auto& field : struct_type->fields()) {
      std::string field_var_name = name + "." + field.name;
      DeclareParameter(field_var_name, field.type);
    }
  }
  return result;
}

void DeclarationVisitor::Visit(VarDeclarationStatement* stmt) {
  std::string variable_name = stmt->name;
  if (!stmt->const_qualified) {
    if (!stmt->type) {
      ReportError(
          "variable declaration is missing type. Only 'const' bindings can "
          "infer the type.");
    }
    const Type* type = declarations()->GetType(*stmt->type);
    if (type->IsConstexpr()) {
      ReportError(
          "cannot declare variable with constexpr type. Use 'const' instead.");
    }
    DeclareVariable(variable_name, type, stmt->const_qualified);
    if (global_context_.verbose()) {
      std::cout << "declared variable " << variable_name << " with type "
                << *type << "\n";
    }
  }

  // const qualified variables are required to be initialized properly.
  if (stmt->const_qualified && !stmt->initializer) {
    std::stringstream stream;
    stream << "local constant \"" << variable_name << "\" is not initialized.";
    ReportError(stream.str());
  }

  if (stmt->initializer) {
    Visit(*stmt->initializer);
    if (global_context_.verbose()) {
      std::cout << "variable has initialization expression at "
                << CurrentPositionAsString() << "\n";
    }
  }
}

void DeclarationVisitor::Visit(ExternConstDeclaration* decl) {
  const Type* type = declarations()->GetType(decl->type);
  if (!type->IsConstexpr()) {
    std::stringstream stream;
    stream << "extern constants must have constexpr type, but found: \""
           << *type << "\"\n";
    ReportError(stream.str());
  }

  declarations()->DeclareExternConstant(decl->name, type, decl->literal);
}

void DeclarationVisitor::Visit(StructDeclaration* decl) {
  std::vector<NameAndType> fields;
  for (auto& field : decl->fields) {
    const Type* field_type = declarations()->GetType(field.type);
    fields.push_back({field.name, field_type});
  }
  declarations()->DeclareStruct(CurrentModule(), decl->name, fields);
}

void DeclarationVisitor::Visit(LogicalOrExpression* expr) {
  {
    Declarations::NodeScopeActivator scope(declarations(), expr->left);
    declarations()->DeclareLabel(kFalseLabelName);
    Visit(expr->left);
  }
  Visit(expr->right);
}

void DeclarationVisitor::Visit(LogicalAndExpression* expr) {
  {
    Declarations::NodeScopeActivator scope(declarations(), expr->left);
    declarations()->DeclareLabel(kTrueLabelName);
    Visit(expr->left);
  }
  Visit(expr->right);
}

void DeclarationVisitor::DeclareExpressionForBranch(Expression* node) {
  Declarations::NodeScopeActivator scope(declarations(), node);
  // Conditional expressions can either explicitly return a bit
  // type, or they can be backed by macros that don't return but
  // take a true and false label. By declaring the labels before
  // visiting the conditional expression, those label-based
  // macro conditionals will be able to find them through normal
  // label lookups.
  declarations()->DeclareLabel(kTrueLabelName);
  declarations()->DeclareLabel(kFalseLabelName);
  Visit(node);
}

void DeclarationVisitor::Visit(ConditionalExpression* expr) {
  DeclareExpressionForBranch(expr->condition);
  PushControlSplit();
  Visit(expr->if_true);
  Visit(expr->if_false);
  auto changed_vars = PopControlSplit();
  global_context_.AddControlSplitChangedVariables(
      expr, declarations()->GetCurrentSpecializationTypeNamesVector(),
      changed_vars);
}

void DeclarationVisitor::Visit(IfStatement* stmt) {
  if (!stmt->is_constexpr) {
    PushControlSplit();
  }
  DeclareExpressionForBranch(stmt->condition);
  Visit(stmt->if_true);
  if (stmt->if_false) Visit(*stmt->if_false);
  if (!stmt->is_constexpr) {
    auto changed_vars = PopControlSplit();
    global_context_.AddControlSplitChangedVariables(
        stmt, declarations()->GetCurrentSpecializationTypeNamesVector(),
        changed_vars);
  }
}

void DeclarationVisitor::Visit(WhileStatement* stmt) {
  Declarations::NodeScopeActivator scope(declarations(), stmt);
  DeclareExpressionForBranch(stmt->condition);
  PushControlSplit();
  Visit(stmt->body);
  auto changed_vars = PopControlSplit();
  global_context_.AddControlSplitChangedVariables(
      stmt, declarations()->GetCurrentSpecializationTypeNamesVector(),
      changed_vars);
}

void DeclarationVisitor::Visit(ForOfLoopStatement* stmt) {
  // Scope for for iteration variable
  Declarations::NodeScopeActivator scope(declarations(), stmt);
  Visit(stmt->var_declaration);
  Visit(stmt->iterable);
  if (stmt->begin) Visit(*stmt->begin);
  if (stmt->end) Visit(*stmt->end);
  PushControlSplit();
  Visit(stmt->body);
  auto changed_vars = PopControlSplit();
  global_context_.AddControlSplitChangedVariables(
      stmt, declarations()->GetCurrentSpecializationTypeNamesVector(),
      changed_vars);
}

void DeclarationVisitor::Visit(ForLoopStatement* stmt) {
  Declarations::NodeScopeActivator scope(declarations(), stmt);
  if (stmt->var_declaration) Visit(*stmt->var_declaration);
  PushControlSplit();

  // Same as DeclareExpressionForBranch, but without the extra scope.
  // If no test expression is present we can not use it for the scope.
  declarations()->DeclareLabel(kTrueLabelName);
  declarations()->DeclareLabel(kFalseLabelName);
  if (stmt->test) Visit(*stmt->test);

  Visit(stmt->body);
  if (stmt->action) Visit(*stmt->action);
  auto changed_vars = PopControlSplit();
  global_context_.AddControlSplitChangedVariables(
      stmt, declarations()->GetCurrentSpecializationTypeNamesVector(),
      changed_vars);
}

void DeclarationVisitor::Visit(TryLabelStatement* stmt) {
  // Activate a new scope to declare handler labels, they should not be
  // visible outside the label block.
  {
    Declarations::NodeScopeActivator scope(declarations(), stmt);

    // Declare labels
    for (LabelBlock* block : stmt->label_blocks) {
      CurrentSourcePosition::Scope scope(block->pos);
      Label* shared_label = declarations()->DeclareLabel(block->label);
      {
        Declarations::NodeScopeActivator scope(declarations(), block->body);
        if (block->parameters.has_varargs) {
          std::stringstream stream;
          stream << "cannot use ... for label parameters";
          ReportError(stream.str());
        }

        size_t i = 0;
        for (auto p : block->parameters.names) {
          const Type* type =
              declarations()->GetType(block->parameters.types[i]);
          if (type->IsConstexpr()) {
            ReportError("no constexpr type allowed for label arguments");
          }

          shared_label->AddVariable(DeclareVariable(p, type, false));
          ++i;
        }
      }
      if (global_context_.verbose()) {
        std::cout << " declaring label " << block->label << "\n";
      }
    }

    Visit(stmt->try_block);
  }

  for (LabelBlock* block : stmt->label_blocks) {
    Visit(block->body);
  }
}

void DeclarationVisitor::GenerateHeader(std::string& file_name) {
  std::stringstream new_contents_stream;
  new_contents_stream
      << "#ifndef V8_BUILTINS_BUILTIN_DEFINITIONS_FROM_DSL_H_\n"
         "#define V8_BUILTINS_BUILTIN_DEFINITIONS_FROM_DSL_H_\n"
         "\n"
         "#define BUILTIN_LIST_FROM_DSL(CPP, API, TFJ, TFC, TFS, TFH, ASM) "
         "\\\n";
  for (auto builtin : torque_builtins_) {
    int firstParameterIndex = 1;
    bool declareParameters = true;
    if (builtin->IsStub()) {
      new_contents_stream << "TFS(" << builtin->name();
    } else {
      new_contents_stream << "TFJ(" << builtin->name();
      if (builtin->IsVarArgsJavaScript()) {
        new_contents_stream
            << ", SharedFunctionInfo::kDontAdaptArgumentsSentinel";
        declareParameters = false;
      } else {
        assert(builtin->IsFixedArgsJavaScript());
        // FixedArg javascript builtins need to offer the parameter
        // count.
        assert(builtin->parameter_names().size() >= 2);
        new_contents_stream << ", " << (builtin->parameter_names().size() - 2);
        // And the receiver is explicitly declared.
        new_contents_stream << ", kReceiver";
        firstParameterIndex = 2;
      }
    }
    if (declareParameters) {
      int index = 0;
      for (auto parameter : builtin->parameter_names()) {
        if (index >= firstParameterIndex) {
          new_contents_stream << ", k" << CamelifyString(parameter);
        }
        index++;
      }
    }
    new_contents_stream << ") \\\n";
  }
  new_contents_stream
      << "\n"
         "#endif  // V8_BUILTINS_BUILTIN_DEFINITIONS_FROM_DSL_H_\n";

  std::string new_contents(new_contents_stream.str());
  ReplaceFileContentsIfDifferent(file_name, new_contents);
}

void DeclarationVisitor::Visit(IdentifierExpression* expr) {
  if (expr->generic_arguments.size() != 0) {
    TypeVector specialization_types;
    for (auto t : expr->generic_arguments) {
      specialization_types.push_back(declarations()->GetType(t));
    }
    // Specialize all versions of the generic, since the exact parameter type
    // list cannot be resolved until the call's parameter expressions are
    // evaluated. This is an overly conservative but simple way to make sure
    // that the correct specialization exists.
    for (auto generic : declarations()->LookupGeneric(expr->name)->list()) {
      CallableNode* callable = generic->declaration()->callable;
      if (generic->declaration()->body) {
        QueueGenericSpecialization({generic, specialization_types}, callable,
                                   callable->signature.get(),
                                   generic->declaration()->body);
      }
    }
  }
}

void DeclarationVisitor::Visit(CallExpression* expr) {
  Visit(&expr->callee);
  for (Expression* arg : expr->arguments) Visit(arg);
}

void DeclarationVisitor::Visit(TypeDeclaration* decl) {
  std::string generates = decl->generates ? *decl->generates : std::string("");
  const AbstractType* type = declarations()->DeclareAbstractType(
      decl->name, generates, {}, decl->extends);

  if (decl->constexpr_generates) {
    std::string constexpr_name = CONSTEXPR_TYPE_PREFIX + decl->name;
    base::Optional<std::string> constexpr_extends;
    if (decl->extends)
      constexpr_extends = CONSTEXPR_TYPE_PREFIX + *decl->extends;
    declarations()->DeclareAbstractType(
        constexpr_name, *decl->constexpr_generates, type, constexpr_extends);
  }
}

void DeclarationVisitor::MarkLocationModified(Expression* location) {
  if (IdentifierExpression* id = IdentifierExpression::cast(location)) {
    const Value* value = declarations()->LookupValue(id->name);
    if (value->IsVariable()) {
      const Variable* variable = Variable::cast(value);
      bool was_live = MarkVariableModified(variable);
      if (was_live && global_context_.verbose()) {
        std::cout << *variable << " was modified in control split at "
                  << PositionAsString(id->pos) << "\n";
      }
    }
  }
}

bool DeclarationVisitor::MarkVariableModified(const Variable* variable) {
  auto e = live_and_changed_variables_.rend();
  auto c = live_and_changed_variables_.rbegin();
  bool was_live_in_preceeding_split = false;
  while (c != e) {
    if (c->live.find(variable) != c->live.end()) {
      c->changed.insert(variable);
      was_live_in_preceeding_split = true;
    }
    c++;
  }
  return was_live_in_preceeding_split;
}

void DeclarationVisitor::DeclareSignature(const Signature& signature) {
  auto type_iterator = signature.parameter_types.types.begin();
  for (auto name : signature.parameter_names) {
    const Type* t(*type_iterator++);
    if (name.size() != 0) {
      DeclareParameter(name, t);
    }
  }
  for (auto& label : signature.labels) {
    auto label_params = label.types;
    Label* new_label = declarations()->DeclareLabel(label.name);
    size_t i = 0;
    for (auto var_type : label_params) {
      if (var_type->IsConstexpr()) {
        ReportError("no constexpr type allowed for label arguments");
      }

      std::string var_name = label.name + std::to_string(i++);
      new_label->AddVariable(DeclareVariable(var_name, var_type, false));
    }
  }
}

void DeclarationVisitor::DeclareSpecializedTypes(const SpecializationKey& key) {
  size_t i = 0;
  Generic* generic = key.first;
  const std::size_t generic_parameter_count =
      generic->declaration()->generic_parameters.size();
  if (generic_parameter_count != key.second.size()) {
    std::stringstream stream;
    stream << "Wrong generic argument count for specialization of \""
           << generic->name() << "\", expected: " << generic_parameter_count
           << ", actual: " << key.second.size();
    ReportError(stream.str());
  }

  for (auto type : key.second) {
    std::string generic_type_name =
        generic->declaration()->generic_parameters[i++];
    declarations()->DeclareType(generic_type_name, type);
  }
}

void DeclarationVisitor::Specialize(const SpecializationKey& key,
                                    CallableNode* callable,
                                    const CallableNodeSignature* signature,
                                    Statement* body) {
  Generic* generic = key.first;

  // TODO(tebbi): The error should point to the source position where the
  // instantiation was requested.
  CurrentSourcePosition::Scope pos_scope(generic->declaration()->pos);
  size_t generic_parameter_count =
      generic->declaration()->generic_parameters.size();
  if (generic_parameter_count != key.second.size()) {
    std::stringstream stream;
    stream << "number of template parameters ("
           << std::to_string(key.second.size())
           << ") to intantiation of generic " << callable->name
           << " doesnt match the generic's declaration ("
           << std::to_string(generic_parameter_count) << ")";
    ReportError(stream.str());
  }

  Signature type_signature;
  {
    // Manually activate the specialized generic's scope when declaring the
    // generic parameter specializations.
    Declarations::GenericScopeActivator namespace_scope(declarations(), key);
    DeclareSpecializedTypes(key);
    type_signature = MakeSignature(signature);
  }

  Visit(callable, type_signature, body);
}

}  // namespace torque
}  // namespace internal
}  // namespace v8
