// Copyright (c) 2019, 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.

#ifndef RUNTIME_VM_COMPILER_BACKEND_BLOCK_BUILDER_H_
#define RUNTIME_VM_COMPILER_BACKEND_BLOCK_BUILDER_H_

#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif  // defined(DART_PRECOMPILED_RUNTIME)

#include "vm/compiler/backend/flow_graph.h"
#include "vm/compiler/backend/il.h"

namespace dart {
namespace compiler {

// Helper class for building basic blocks in SSA form.
class BlockBuilder : public ValueObject {
 public:
  BlockBuilder(FlowGraph* flow_graph,
               BlockEntryInstr* entry,
               bool with_frame = true)
      : flow_graph_(flow_graph),
        source_(InstructionSource(flow_graph_->function().token_pos(),
                                  flow_graph->inlining_id())),
        entry_(entry),
        current_(entry),
        dummy_env_(new Environment(0, 0, 0, flow_graph->function(), nullptr)),
        with_frame_(with_frame) {
    // Some graph transformations use environments from block entries.
    entry->SetEnvironment(dummy_env_);
  }

  Definition* AddToInitialDefinitions(Definition* def) {
    flow_graph_->AllocateSSAIndex(def);
    auto normal_entry = flow_graph_->graph_entry()->normal_entry();
    flow_graph_->AddToInitialDefinitions(normal_entry, def);
    return def;
  }

  template <typename T>
  T* AddDefinition(T* def) {
    flow_graph_->AllocateSSAIndex(def);
    AddInstruction(def);
    return def;
  }

  template <typename T>
  T* AddInstruction(T* instr) {
    if (instr->ComputeCanDeoptimize() ||
        instr->ComputeCanDeoptimizeAfterCall() ||
        instr->CanBecomeDeoptimizationTarget()) {
      // All instructions that can deoptimize must have an environment attached
      // to them.
      instr->SetEnvironment(dummy_env_);
    }
    current_ = current_->AppendInstruction(instr);
    return instr;
  }

  const Function& function() const { return flow_graph_->function(); }

  DartReturnInstr* AddReturn(Value* value) {
    const auto& function = flow_graph_->function();
    const auto representation = FlowGraph::ReturnRepresentationOf(function);
    return AddReturn(value, representation);
  }

  DartReturnInstr* AddReturn(Value* value, Representation representation) {
    DartReturnInstr* instr = new DartReturnInstr(
        Source(), value, CompilerState::Current().GetNextDeoptId(),
        representation);
    AddInstruction(instr);
    entry_->set_last_instruction(instr);
    return instr;
  }

  Definition* AddParameter(intptr_t index) {
    const auto [location, representation] =
        flow_graph_->GetDirectParameterInfoAt(index);
    return AddParameter(index, representation,
                        with_frame_ ? location : location.ToEntrySpRelative());
  }

  Definition* AddParameter(intptr_t index,
                           Representation representation,
                           Location location = Location()) {
    auto normal_entry = flow_graph_->graph_entry()->normal_entry();
    return AddToInitialDefinitions(
        new ParameterInstr(normal_entry,
                           /*env_index=*/index,
                           /*param_index=*/index, location, representation));
  }

  TokenPosition TokenPos() const { return source_.token_pos; }
  const InstructionSource& Source() const { return source_; }

  Definition* AddNullDefinition() {
    return flow_graph_->GetConstant(Object::ZoneHandle());
  }

  Definition* AddUnboxInstr(Representation rep,
                            Value* value,
                            UnboxInstr::ValueMode value_mode) {
    // Unbox floats by first unboxing a double then converting it to a float.
    auto const unbox_rep = rep == kUnboxedFloat
                               ? kUnboxedDouble
                               : Boxing::NativeRepresentation(rep);
    Definition* unboxed_value = AddDefinition(
        UnboxInstr::Create(unbox_rep, value, DeoptId::kNone, value_mode));
    ASSERT((rep == unbox_rep) || !unboxed_value->IsUnboxInteger() ||
           (RepresentationUtils::ValueSize(rep) <
            RepresentationUtils::ValueSize(unbox_rep)));
    if (value_mode == UnboxInstr::ValueMode::kHasValidType) {
      // The type of |value| has already been checked and it is safe to
      // adjust reaching type. This is done manually because there is no type
      // propagation when building intrinsics.
      unboxed_value->AsUnbox()->value()->SetReachingType(
          new CompileType(CompileType::FromUnboxedRepresentation(rep)));
    }
    if (rep == kUnboxedFloat) {
      unboxed_value = AddDefinition(
          new DoubleToFloatInstr(new Value(unboxed_value), DeoptId::kNone));
    }
    return unboxed_value;
  }

  Definition* AddUnboxInstr(Representation rep,
                            Definition* boxed,
                            UnboxInstr::ValueMode value_mode) {
    return AddUnboxInstr(rep, new Value(boxed), value_mode);
  }

  BranchInstr* AddBranch(ConditionInstr* cond,
                         TargetEntryInstr* true_successor,
                         TargetEntryInstr* false_successor) {
    auto branch =
        new BranchInstr(cond, CompilerState::Current().GetNextDeoptId());
    // Some graph transformations use environments from branches.
    branch->SetEnvironment(dummy_env_);
    current_->AppendInstruction(branch);
    current_ = nullptr;

    *branch->true_successor_address() = true_successor;
    *branch->false_successor_address() = false_successor;

    return branch;
  }

  void AddPhi(PhiInstr* phi) {
    flow_graph_->AllocateSSAIndex(phi);
    phi->mark_alive();
    entry_->AsJoinEntry()->InsertPhi(phi);
  }

  Instruction* last() const { return current_; }

 private:
  FlowGraph* const flow_graph_;
  const InstructionSource source_;
  BlockEntryInstr* entry_;
  Instruction* current_;
  Environment* dummy_env_;
  const bool with_frame_;
};

}  // namespace compiler
}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_BACKEND_BLOCK_BUILDER_H_
