// Copyright 2018 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/csa-generator.h"

#include "src/globals.h"
#include "src/torque/type-oracle.h"
#include "src/torque/utils.h"

namespace v8 {
namespace internal {
namespace torque {

base::Optional<Stack<std::string>> CSAGenerator::EmitGraph(
    Stack<std::string> parameters) {
  for (Block* block : cfg_.blocks()) {
    out_ << "  compiler::CodeAssemblerParameterizedLabel<";
    PrintCommaSeparatedList(out_, block->InputTypes(), [](const Type* t) {
      return t->GetGeneratedTNodeTypeName();
    });
    out_ << "> " << BlockName(block) << "(&ca_, compiler::CodeAssemblerLabel::"
         << (block->IsDeferred() ? "kDeferred" : "kNonDeferred") << ");\n";
  }

  EmitInstruction(GotoInstruction{cfg_.start()}, &parameters);
  for (Block* block : cfg_.blocks()) {
    if (cfg_.end() && *cfg_.end() == block) continue;
    out_ << "\n  if (" << BlockName(block) << ".is_used()) {\n";
    EmitBlock(block);
    out_ << "  }\n";
  }
  if (cfg_.end()) {
    out_ << "\n";
    return EmitBlock(*cfg_.end());
  }
  return base::nullopt;
}

Stack<std::string> CSAGenerator::EmitBlock(const Block* block) {
  Stack<std::string> stack;
  for (const Type* t : block->InputTypes()) {
    stack.Push(FreshNodeName());
    out_ << "    compiler::TNode<" << t->GetGeneratedTNodeTypeName() << "> "
         << stack.Top() << ";\n";
  }
  out_ << "    ca_.Bind(&" << BlockName(block);
  for (const std::string& name : stack) {
    out_ << ", &" << name;
  }
  out_ << ");\n";
  for (const Instruction& instruction : block->instructions()) {
    EmitInstruction(instruction, &stack);
  }
  return stack;
}

void CSAGenerator::EmitSourcePosition(SourcePosition pos, bool always_emit) {
  const std::string& file = SourceFileMap::GetSource(pos.source);
  if (always_emit || !previous_position_.CompareStartIgnoreColumn(pos)) {
    // Lines in Torque SourcePositions are zero-based, while the
    // CodeStubAssembler and downwind systems are one-based.
    out_ << "    ca_.SetSourcePosition(\"" << file << "\", "
         << (pos.start.line + 1) << ");\n";
    previous_position_ = pos;
  }
}

void CSAGenerator::EmitInstruction(const Instruction& instruction,
                                   Stack<std::string>* stack) {
  EmitSourcePosition(instruction->pos);
  switch (instruction.kind()) {
#define ENUM_ITEM(T)          \
  case InstructionKind::k##T: \
    return EmitInstruction(instruction.Cast<T>(), stack);
    TORQUE_INSTRUCTION_LIST(ENUM_ITEM)
#undef ENUM_ITEM
  }
}

void CSAGenerator::EmitInstruction(const PeekInstruction& instruction,
                                   Stack<std::string>* stack) {
  stack->Push(stack->Peek(instruction.slot));
}

void CSAGenerator::EmitInstruction(const PokeInstruction& instruction,
                                   Stack<std::string>* stack) {
  stack->Poke(instruction.slot, stack->Top());
  stack->Pop();
}

void CSAGenerator::EmitInstruction(const DeleteRangeInstruction& instruction,
                                   Stack<std::string>* stack) {
  stack->DeleteRange(instruction.range);
}

void CSAGenerator::EmitInstruction(
    const PushUninitializedInstruction& instruction,
    Stack<std::string>* stack) {
  // TODO(tebbi): This can trigger an error in CSA if it is used. Instead, we
  // should prevent usage of uninitialized in the type system. This
  // requires "if constexpr" being evaluated at Torque time.
  stack->Push("ca_.Uninitialized<" +
              instruction.type->GetGeneratedTNodeTypeName() + ">()");
}

void CSAGenerator::EmitInstruction(
    const PushBuiltinPointerInstruction& instruction,
    Stack<std::string>* stack) {
  stack->Push("ca_.UncheckedCast<BuiltinPtr>(ca_.SmiConstant(Builtins::k" +
              instruction.external_name + "))");
}

void CSAGenerator::EmitInstruction(
    const NamespaceConstantInstruction& instruction,
    Stack<std::string>* stack) {
  const Type* type = instruction.constant->type();
  std::vector<std::string> results;
  for (const Type* lowered : LowerType(type)) {
    results.push_back(FreshNodeName());
    stack->Push(results.back());
    out_ << "    compiler::TNode<" << lowered->GetGeneratedTNodeTypeName()
         << "> " << stack->Top() << ";\n";
    out_ << "    USE(" << stack->Top() << ");\n";
  }
  out_ << "    ";
  if (type->IsStructType()) {
    out_ << "std::tie(";
    PrintCommaSeparatedList(out_, results);
    out_ << ") = ";
  } else if (results.size() == 1) {
    out_ << results[0] << " = ";
  }
  out_ << instruction.constant->ExternalAssemblerName() << "(state_)."
       << instruction.constant->name()->value << "()";
  if (type->IsStructType()) {
    out_ << ".Flatten();\n";
  } else {
    out_ << ";\n";
  }
}

void CSAGenerator::ProcessArgumentsCommon(
    const TypeVector& parameter_types, std::vector<std::string>* args,
    std::vector<std::string>* constexpr_arguments, Stack<std::string>* stack) {
  for (auto it = parameter_types.rbegin(); it != parameter_types.rend(); ++it) {
    const Type* type = *it;
    VisitResult arg;
    if (type->IsConstexpr()) {
      args->push_back(std::move(constexpr_arguments->back()));
      constexpr_arguments->pop_back();
    } else {
      std::stringstream s;
      size_t slot_count = LoweredSlotCount(type);
      VisitResult arg = VisitResult(type, stack->TopRange(slot_count));
      EmitCSAValue(arg, *stack, s);
      args->push_back(s.str());
      stack->PopMany(slot_count);
    }
  }
  std::reverse(args->begin(), args->end());
}

void CSAGenerator::EmitInstruction(const CallIntrinsicInstruction& instruction,
                                   Stack<std::string>* stack) {
  std::vector<std::string> constexpr_arguments =
      instruction.constexpr_arguments;
  std::vector<std::string> args;
  TypeVector parameter_types =
      instruction.intrinsic->signature().parameter_types.types;
  ProcessArgumentsCommon(parameter_types, &args, &constexpr_arguments, stack);

  Stack<std::string> pre_call_stack = *stack;
  const Type* return_type = instruction.intrinsic->signature().return_type;
  std::vector<std::string> results;
  for (const Type* type : LowerType(return_type)) {
    results.push_back(FreshNodeName());
    stack->Push(results.back());
    out_ << "    compiler::TNode<" << type->GetGeneratedTNodeTypeName() << "> "
         << stack->Top() << ";\n";
    out_ << "    USE(" << stack->Top() << ");\n";
  }
  out_ << "    ";

  if (return_type->IsStructType()) {
    out_ << "std::tie(";
    PrintCommaSeparatedList(out_, results);
    out_ << ") = ";
  } else {
    if (results.size() == 1) {
      out_ << results[0] << " = ";
    }
  }

  if (instruction.intrinsic->ExternalName() == "%RawDownCast") {
    if (parameter_types.size() != 1) {
      ReportError("%RawDownCast must take a single parameter");
    }
    if (!return_type->IsSubtypeOf(parameter_types[0])) {
      ReportError("%RawDownCast error: ", *return_type, " is not a subtype of ",
                  *parameter_types[0]);
    }
    if (return_type->IsSubtypeOf(TypeOracle::GetTaggedType())) {
      if (return_type->GetGeneratedTNodeTypeName() !=
          parameter_types[0]->GetGeneratedTNodeTypeName()) {
        out_ << "TORQUE_CAST";
      }
    }
  } else if (instruction.intrinsic->ExternalName() == "%FromConstexpr") {
    if (parameter_types.size() != 1 || !parameter_types[0]->IsConstexpr()) {
      ReportError(
          "%FromConstexpr must take a single parameter with constexpr "
          "type");
    }
    if (return_type->IsConstexpr()) {
      ReportError("%FromConstexpr must return a non-constexpr type");
    }
    if (return_type->IsSubtypeOf(TypeOracle::GetSmiType())) {
      out_ << "ca_.SmiConstant";
    } else if (return_type->IsSubtypeOf(TypeOracle::GetNumberType())) {
      out_ << "ca_.NumberConstant";
    } else if (return_type->IsSubtypeOf(TypeOracle::GetStringType())) {
      out_ << "ca_.StringConstant";
    } else if (return_type->IsSubtypeOf(TypeOracle::GetObjectType())) {
      ReportError(
          "%FromConstexpr cannot cast to subclass of HeapObject unless it's a "
          "String or Number");
    } else if (return_type->IsSubtypeOf(TypeOracle::GetIntPtrType())) {
      out_ << "ca_.IntPtrConstant";
    } else if (return_type->IsSubtypeOf(TypeOracle::GetUIntPtrType())) {
      out_ << "ca_.UintPtrConstant";
    } else if (return_type->IsSubtypeOf(TypeOracle::GetInt32Type())) {
      out_ << "ca_.Int32Constant";
    } else {
      std::stringstream s;
      s << "%FromConstexpr does not support return type " << *return_type;
      ReportError(s.str());
    }
  } else if (instruction.intrinsic->ExternalName() ==
             "%GetAllocationBaseSize") {
    if (instruction.specialization_types.size() != 1) {
      ReportError(
          "incorrect number of specialization classes for "
          "%GetAllocationBaseSize (should be one)");
    }
    const ClassType* class_type =
        ClassType::cast(instruction.specialization_types[0]);
    // Special case classes that may not always have a fixed size (e.g.
    // JSObjects). Their size must be fetched from the map.
    if (class_type != TypeOracle::GetJSObjectType()) {
      out_ << "CodeStubAssembler(state_).IntPtrConstant((";
      args[0] = std::to_string(class_type->size());
    } else {
      out_ << "CodeStubAssembler(state_).TimesTaggedSize(CodeStubAssembler("
              "state_).LoadMapInstanceSizeInWords(";
    }
  } else if (instruction.intrinsic->ExternalName() == "%Allocate") {
    out_ << "ca_.UncheckedCast<" << return_type->GetGeneratedTNodeTypeName()
         << ">(CodeStubAssembler(state_).Allocate";
  } else if (instruction.intrinsic->ExternalName() ==
             "%AllocateInternalClass") {
    out_ << "CodeStubAssembler(state_).AllocateUninitializedFixedArray";
  } else {
    ReportError("no built in intrinsic with name " +
                instruction.intrinsic->ExternalName());
  }

  out_ << "(";
  PrintCommaSeparatedList(out_, args);
  if (instruction.intrinsic->ExternalName() == "%Allocate") out_ << ")";
  if (instruction.intrinsic->ExternalName() == "%GetAllocationBaseSize")
    out_ << "))";
  if (return_type->IsStructType()) {
    out_ << ").Flatten();\n";
  } else {
    out_ << ");\n";
  }
  if (instruction.intrinsic->ExternalName() == "%Allocate") {
    out_ << "    CodeStubAssembler(state_).InitializeFieldsWithRoot("
         << results[0] << ", ";
    out_ << "CodeStubAssembler(state_).IntPtrConstant("
         << std::to_string(ClassType::cast(return_type)->size()) << "), ";
    PrintCommaSeparatedList(out_, args);
    out_ << ", RootIndex::kUndefinedValue);\n";
  }
}

void CSAGenerator::EmitInstruction(const CallCsaMacroInstruction& instruction,
                                   Stack<std::string>* stack) {
  std::vector<std::string> constexpr_arguments =
      instruction.constexpr_arguments;
  std::vector<std::string> args;
  TypeVector parameter_types =
      instruction.macro->signature().parameter_types.types;
  ProcessArgumentsCommon(parameter_types, &args, &constexpr_arguments, stack);

  Stack<std::string> pre_call_stack = *stack;
  const Type* return_type = instruction.macro->signature().return_type;
  std::vector<std::string> results;
  for (const Type* type : LowerType(return_type)) {
    results.push_back(FreshNodeName());
    stack->Push(results.back());
    out_ << "    compiler::TNode<" << type->GetGeneratedTNodeTypeName() << "> "
         << stack->Top() << ";\n";
    out_ << "    USE(" << stack->Top() << ");\n";
  }
  std::string catch_name =
      PreCallableExceptionPreparation(instruction.catch_block);
  out_ << "    ";
  if (return_type->IsStructType()) {
    out_ << "std::tie(";
    PrintCommaSeparatedList(out_, results);
    out_ << ") = ";
  } else {
    if (results.size() == 1) {
      out_ << results[0] << " = ca_.UncheckedCast<"
           << return_type->GetGeneratedTNodeTypeName() << ">(";
    }
  }
  out_ << instruction.macro->external_assembler_name() << "(state_)."
       << instruction.macro->ExternalName() << "(";
  PrintCommaSeparatedList(out_, args);
  if (return_type->IsStructType()) {
    out_ << ").Flatten();\n";
  } else {
    if (results.size() == 1) out_ << ")";
    out_ << ");\n";
  }
  PostCallableExceptionPreparation(catch_name, return_type,
                                   instruction.catch_block, &pre_call_stack);
}

void CSAGenerator::EmitInstruction(
    const CallCsaMacroAndBranchInstruction& instruction,
    Stack<std::string>* stack) {
  std::vector<std::string> constexpr_arguments =
      instruction.constexpr_arguments;
  std::vector<std::string> args;
  TypeVector parameter_types =
      instruction.macro->signature().parameter_types.types;
  ProcessArgumentsCommon(parameter_types, &args, &constexpr_arguments, stack);

  Stack<std::string> pre_call_stack = *stack;
  std::vector<std::string> results;
  const Type* return_type = instruction.macro->signature().return_type;
  if (return_type != TypeOracle::GetNeverType()) {
    for (const Type* type :
         LowerType(instruction.macro->signature().return_type)) {
      results.push_back(FreshNodeName());
      out_ << "    compiler::TNode<" << type->GetGeneratedTNodeTypeName()
           << "> " << results.back() << ";\n";
      out_ << "    USE(" << results.back() << ");\n";
    }
  }

  std::vector<std::string> label_names;
  std::vector<std::vector<std::string>> var_names;
  const LabelDeclarationVector& labels = instruction.macro->signature().labels;
  DCHECK_EQ(labels.size(), instruction.label_blocks.size());
  for (size_t i = 0; i < labels.size(); ++i) {
    TypeVector label_parameters = labels[i].types;
    label_names.push_back("label" + std::to_string(i));
    var_names.push_back({});
    for (size_t j = 0; j < label_parameters.size(); ++j) {
      var_names[i].push_back("result_" + std::to_string(i) + "_" +
                             std::to_string(j));
      out_ << "    compiler::TypedCodeAssemblerVariable<"
           << label_parameters[j]->GetGeneratedTNodeTypeName() << "> "
           << var_names[i][j] << "(&ca_);\n";
    }
    out_ << "    compiler::CodeAssemblerLabel " << label_names[i]
         << "(&ca_);\n";
  }

  std::string catch_name =
      PreCallableExceptionPreparation(instruction.catch_block);
  out_ << "    ";
  if (results.size() == 1) {
    out_ << results[0] << " = ";
  } else if (results.size() > 1) {
    out_ << "std::tie(";
    PrintCommaSeparatedList(out_, results);
    out_ << ") = ";
  }
  out_ << instruction.macro->external_assembler_name() << "(state_)."
       << instruction.macro->ExternalName() << "(";
  PrintCommaSeparatedList(out_, args);
  bool first = args.empty();
  for (size_t i = 0; i < label_names.size(); ++i) {
    if (!first) out_ << ", ";
    out_ << "&" << label_names[i];
    first = false;
    for (size_t j = 0; j < var_names[i].size(); ++j) {
      out_ << ", &" << var_names[i][j];
    }
  }
  if (return_type->IsStructType()) {
    out_ << ").Flatten();\n";
  } else {
    out_ << ");\n";
  }

  PostCallableExceptionPreparation(catch_name, return_type,
                                   instruction.catch_block, &pre_call_stack);

  if (instruction.return_continuation) {
    out_ << "    ca_.Goto(&" << BlockName(*instruction.return_continuation);
    for (const std::string& value : *stack) {
      out_ << ", " << value;
    }
    for (const std::string& result : results) {
      out_ << ", " << result;
    }
    out_ << ");\n";
  }
  for (size_t i = 0; i < label_names.size(); ++i) {
    out_ << "    if (" << label_names[i] << ".is_used()) {\n";
    out_ << "      ca_.Bind(&" << label_names[i] << ");\n";
    out_ << "      ca_.Goto(&" << BlockName(instruction.label_blocks[i]);
    for (const std::string& value : *stack) {
      out_ << ", " << value;
    }
    for (const std::string& var : var_names[i]) {
      out_ << ", " << var << ".value()";
    }
    out_ << ");\n";

    out_ << "    }\n";
  }
}

void CSAGenerator::EmitInstruction(const CallBuiltinInstruction& instruction,
                                   Stack<std::string>* stack) {
  std::vector<std::string> arguments = stack->PopMany(instruction.argc);
  std::vector<const Type*> result_types =
      LowerType(instruction.builtin->signature().return_type);
  if (instruction.is_tailcall) {
    out_ << "   CodeStubAssembler(state_).TailCallBuiltin(Builtins::k"
         << instruction.builtin->ExternalName() << ", ";
    PrintCommaSeparatedList(out_, arguments);
    out_ << ");\n";
  } else {
    std::string result_name = FreshNodeName();
    if (result_types.size() == 1) {
      out_ << "    compiler::TNode<"
           << result_types[0]->GetGeneratedTNodeTypeName() << "> "
           << result_name << ";\n";
    }
    std::string catch_name =
        PreCallableExceptionPreparation(instruction.catch_block);
    Stack<std::string> pre_call_stack = *stack;
    if (result_types.size() == 1) {
      std::string generated_type = result_types[0]->GetGeneratedTNodeTypeName();
      stack->Push(result_name);
      out_ << "    " << result_name << " = ";
      if (generated_type != "Object") out_ << "TORQUE_CAST(";
      out_ << "CodeStubAssembler(state_).CallBuiltin(Builtins::k"
           << instruction.builtin->ExternalName() << ", ";
      PrintCommaSeparatedList(out_, arguments);
      if (generated_type != "Object") out_ << ")";
      out_ << ");\n";
      out_ << "    USE(" << result_name << ");\n";
    } else {
      DCHECK_EQ(0, result_types.size());
      // TODO(tebbi): Actually, builtins have to return a value, so we should
      // not have to handle this case.
      out_ << "    CodeStubAssembler(state_).CallBuiltin(Builtins::k"
           << instruction.builtin->ExternalName() << ", ";
      PrintCommaSeparatedList(out_, arguments);
      out_ << ");\n";
    }
    PostCallableExceptionPreparation(
        catch_name,
        result_types.size() == 0 ? TypeOracle::GetVoidType() : result_types[0],
        instruction.catch_block, &pre_call_stack);
  }
}

void CSAGenerator::EmitInstruction(
    const CallBuiltinPointerInstruction& instruction,
    Stack<std::string>* stack) {
  std::vector<std::string> function_and_arguments =
      stack->PopMany(1 + instruction.argc);
  std::vector<const Type*> result_types =
      LowerType(instruction.type->return_type());
  if (result_types.size() != 1) {
    ReportError("builtins must have exactly one result");
  }
  if (instruction.is_tailcall) {
    ReportError("tail-calls to builtin pointers are not supported");
  }

  stack->Push(FreshNodeName());
  std::string generated_type = result_types[0]->GetGeneratedTNodeTypeName();
  out_ << "    compiler::TNode<" << generated_type << "> " << stack->Top()
       << " = ";
  if (generated_type != "Object") out_ << "TORQUE_CAST(";
  out_ << "CodeStubAssembler(state_).CallBuiltinPointer(Builtins::"
          "CallableFor(ca_."
          "isolate(),"
          "ExampleBuiltinForTorqueFunctionPointerType("
       << instruction.type->function_pointer_type_id() << ")).descriptor(), ";
  PrintCommaSeparatedList(out_, function_and_arguments);
  out_ << ")";
  if (generated_type != "Object") out_ << ")";
  out_ << "; \n";
  out_ << "    USE(" << stack->Top() << ");\n";
}

std::string CSAGenerator::PreCallableExceptionPreparation(
    base::Optional<Block*> catch_block) {
  std::string catch_name;
  if (catch_block) {
    catch_name = FreshCatchName();
    out_ << "    compiler::CodeAssemblerExceptionHandlerLabel " << catch_name
         << "_label(&ca_, compiler::CodeAssemblerLabel::kDeferred);\n";
    out_ << "    { compiler::CodeAssemblerScopedExceptionHandler s(&ca_, &"
         << catch_name << "_label);\n";
  }
  return catch_name;
}

void CSAGenerator::PostCallableExceptionPreparation(
    const std::string& catch_name, const Type* return_type,
    base::Optional<Block*> catch_block, Stack<std::string>* stack) {
  if (catch_block) {
    std::string block_name = BlockName(*catch_block);
    out_ << "    }\n";
    out_ << "    if (" << catch_name << "_label.is_used()) {\n";
    out_ << "      compiler::CodeAssemblerLabel " << catch_name
         << "_skip(&ca_);\n";
    if (!return_type->IsNever()) {
      out_ << "      ca_.Goto(&" << catch_name << "_skip);\n";
    }
    out_ << "      compiler::TNode<Object> " << catch_name
         << "_exception_object;\n";
    out_ << "      ca_.Bind(&" << catch_name << "_label, &" << catch_name
         << "_exception_object);\n";
    out_ << "      ca_.Goto(&" << block_name;
    for (size_t i = 0; i < stack->Size(); ++i) {
      out_ << ", " << stack->begin()[i];
    }
    out_ << ", " << catch_name << "_exception_object);\n";
    if (!return_type->IsNever()) {
      out_ << "      ca_.Bind(&" << catch_name << "_skip);\n";
    }
    out_ << "    }\n";
  }
}

void CSAGenerator::EmitInstruction(const CallRuntimeInstruction& instruction,
                                   Stack<std::string>* stack) {
  std::vector<std::string> arguments = stack->PopMany(instruction.argc);
  const Type* return_type =
      instruction.runtime_function->signature().return_type;
  std::vector<const Type*> result_types;
  if (return_type != TypeOracle::GetNeverType()) {
    result_types = LowerType(return_type);
  }
  if (result_types.size() > 1) {
    ReportError("runtime function must have at most one result");
  }
  if (instruction.is_tailcall) {
    out_ << "    CodeStubAssembler(state_).TailCallRuntime(Runtime::k"
         << instruction.runtime_function->ExternalName() << ", ";
    PrintCommaSeparatedList(out_, arguments);
    out_ << ");\n";
  } else {
    std::string result_name = FreshNodeName();
    if (result_types.size() == 1) {
      out_ << "    compiler::TNode<"
           << result_types[0]->GetGeneratedTNodeTypeName() << "> "
           << result_name << ";\n";
    }
    std::string catch_name =
        PreCallableExceptionPreparation(instruction.catch_block);
    Stack<std::string> pre_call_stack = *stack;
    if (result_types.size() == 1) {
      stack->Push(result_name);
      out_ << "    " << result_name
           << " = TORQUE_CAST(CodeStubAssembler(state_).CallRuntime(Runtime::k"
           << instruction.runtime_function->ExternalName() << ", ";
      PrintCommaSeparatedList(out_, arguments);
      out_ << "));\n";
      out_ << "    USE(" << result_name << ");\n";
    } else {
      DCHECK_EQ(0, result_types.size());
      out_ << "    CodeStubAssembler(state_).CallRuntime(Runtime::k"
           << instruction.runtime_function->ExternalName() << ", ";
      PrintCommaSeparatedList(out_, arguments);
      out_ << ");\n";
      if (return_type == TypeOracle::GetNeverType()) {
        out_ << "    CodeStubAssembler(state_).Unreachable();\n";
      } else {
        DCHECK(return_type == TypeOracle::GetVoidType());
      }
    }
    PostCallableExceptionPreparation(catch_name, return_type,
                                     instruction.catch_block, &pre_call_stack);
  }
}

void CSAGenerator::EmitInstruction(const BranchInstruction& instruction,
                                   Stack<std::string>* stack) {
  out_ << "    ca_.Branch(" << stack->Pop() << ", &"
       << BlockName(instruction.if_true) << ", &"
       << BlockName(instruction.if_false);
  for (const std::string& value : *stack) {
    out_ << ", " << value;
  }
  out_ << ");\n";
}

void CSAGenerator::EmitInstruction(
    const ConstexprBranchInstruction& instruction, Stack<std::string>* stack) {
  out_ << "    if ((" << instruction.condition << ")) {\n";
  out_ << "      ca_.Goto(&" << BlockName(instruction.if_true);
  for (const std::string& value : *stack) {
    out_ << ", " << value;
  }
  out_ << ");\n";
  out_ << "    } else {\n";
  out_ << "      ca_.Goto(&" << BlockName(instruction.if_false);
  for (const std::string& value : *stack) {
    out_ << ", " << value;
  }
  out_ << ");\n";

  out_ << "    }\n";
}

void CSAGenerator::EmitInstruction(const GotoInstruction& instruction,
                                   Stack<std::string>* stack) {
  out_ << "    ca_.Goto(&" << BlockName(instruction.destination);
  for (const std::string& value : *stack) {
    out_ << ", " << value;
  }
  out_ << ");\n";
}

void CSAGenerator::EmitInstruction(const GotoExternalInstruction& instruction,
                                   Stack<std::string>* stack) {
  for (auto it = instruction.variable_names.rbegin();
       it != instruction.variable_names.rend(); ++it) {
    out_ << "    *" << *it << " = " << stack->Pop() << ";\n";
  }
  out_ << "    ca_.Goto(" << instruction.destination << ");\n";
}

void CSAGenerator::EmitInstruction(const ReturnInstruction& instruction,
                                   Stack<std::string>* stack) {
  if (*linkage_ == Builtin::kVarArgsJavaScript) {
    out_ << "    " << ARGUMENTS_VARIABLE_STRING << "->PopAndReturn(";
  } else {
    out_ << "    CodeStubAssembler(state_).Return(";
  }
  out_ << stack->Pop() << ");\n";
}

void CSAGenerator::EmitInstruction(
    const PrintConstantStringInstruction& instruction,
    Stack<std::string>* stack) {
  out_ << "    CodeStubAssembler(state_).Print("
       << StringLiteralQuote(instruction.message) << ");\n";
}

void CSAGenerator::EmitInstruction(const AbortInstruction& instruction,
                                   Stack<std::string>* stack) {
  switch (instruction.kind) {
    case AbortInstruction::Kind::kUnreachable:
      DCHECK(instruction.message.empty());
      out_ << "    CodeStubAssembler(state_).Unreachable();\n";
      break;
    case AbortInstruction::Kind::kDebugBreak:
      DCHECK(instruction.message.empty());
      out_ << "    CodeStubAssembler(state_).DebugBreak();\n";
      break;
    case AbortInstruction::Kind::kAssertionFailure: {
      std::string file =
          StringLiteralQuote(SourceFileMap::GetSource(instruction.pos.source));
      out_ << "    CodeStubAssembler(state_).FailAssert("
           << StringLiteralQuote(instruction.message) << ", " << file << ", "
           << instruction.pos.start.line + 1 << ");\n";
      break;
    }
  }
}

void CSAGenerator::EmitInstruction(const UnsafeCastInstruction& instruction,
                                   Stack<std::string>* stack) {
  stack->Poke(stack->AboveTop() - 1,
              "ca_.UncheckedCast<" +
                  instruction.destination_type->GetGeneratedTNodeTypeName() +
                  ">(" + stack->Top() + ")");
}

void CSAGenerator::EmitInstruction(
    const LoadObjectFieldInstruction& instruction, Stack<std::string>* stack) {
  const Field& field =
      instruction.class_type->LookupField(instruction.field_name);
  std::string result_name = FreshNodeName();

  size_t field_size;
  std::string size_string;
  std::string machine_type;
  std::tie(field_size, size_string, machine_type) =
      field.GetFieldSizeInformation();

  if (instruction.class_type->IsExtern()) {
    out_ << field.name_and_type.type->GetGeneratedTypeName() << " "
         << result_name << " = ca_.UncheckedCast<"
         << field.name_and_type.type->GetGeneratedTNodeTypeName()
         << ">(CodeStubAssembler(state_).LoadObjectField(" << stack->Top()
         << ", " << field.aggregate->GetGeneratedTNodeTypeName() << "::k"
         << CamelifyString(field.name_and_type.name) << "Offset, "
         << machine_type + "));\n";
  } else {
    out_ << field.name_and_type.type->GetGeneratedTypeName() << " "
         << result_name << " = ca_.UncheckedCast<"
         << field.name_and_type.type->GetGeneratedTNodeTypeName()
         << ">(CodeStubAssembler(state_).UnsafeLoadFixedArrayElement("
         << stack->Top() << ", " << (field.offset / kTaggedSize) << "));\n";
  }
  stack->Poke(stack->AboveTop() - 1, result_name);
}

void CSAGenerator::EmitInstruction(
    const StoreObjectFieldInstruction& instruction, Stack<std::string>* stack) {
  auto value = stack->Pop();
  auto object = stack->Pop();
  stack->Push(value);
  const Field& field =
      instruction.class_type->LookupField(instruction.field_name);
  if (instruction.class_type->IsExtern()) {
    if (field.name_and_type.type->IsSubtypeOf(TypeOracle::GetTaggedType())) {
      if (field.offset == 0) {
        out_ << "    CodeStubAssembler(state_).StoreMap(" << object << ", "
             << value << ");\n";
      } else {
        out_ << "    CodeStubAssembler(state_).StoreObjectField(" << object
             << ", " << field.offset << ", " << value << ");\n";
      }
    } else {
      size_t field_size;
      std::string size_string;
      std::string machine_type;
      std::tie(field_size, size_string, machine_type) =
          field.GetFieldSizeInformation();
      if (field.offset == 0) {
        ReportError("the first field in a class object must be a map");
      }
      out_ << "    CodeStubAssembler(state_).StoreObjectFieldNoWriteBarrier("
           << object << ", " << field.offset << ", " << value << ", "
           << machine_type << ".representation());\n";
    }
  } else {
    out_ << "    CodeStubAssembler(state_).UnsafeStoreFixedArrayElement("
         << object << ", " << (field.offset / kTaggedSize) << ", " << value
         << ");\n";
  }
}

// static
void CSAGenerator::EmitCSAValue(VisitResult result,
                                const Stack<std::string>& values,
                                std::ostream& out) {
  if (!result.IsOnStack()) {
    out << result.constexpr_value();
  } else if (auto* struct_type = StructType::DynamicCast(result.type())) {
    out << struct_type->GetGeneratedTypeName() << "{";
    bool first = true;
    for (auto& field : struct_type->fields()) {
      if (!first) {
        out << ", ";
      }
      first = false;
      EmitCSAValue(ProjectStructField(result, field.name_and_type.name), values,
                   out);
    }
    out << "}";
  } else {
    DCHECK_EQ(1, result.stack_range().Size());
    out << "compiler::TNode<" << result.type()->GetGeneratedTNodeTypeName()
        << ">{" << values.Peek(result.stack_range().begin()) << "}";
  }
}

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