// Copyright 2014 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/base/adapters.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/liveness-analyzer.h"
#include "src/compiler/node.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/state-values-utils.h"

namespace v8 {
namespace internal {
namespace compiler {

LivenessAnalyzer::LivenessAnalyzer(size_t local_count, bool has_accumulator,
                                   Zone* zone)
    : zone_(zone),
      blocks_(zone),
      local_count_(local_count),
      has_accumulator_(has_accumulator),
      queue_(zone) {}

void LivenessAnalyzer::Print(std::ostream& os) {
  for (auto block : blocks_) {
    block->Print(os);
    os << std::endl;
  }
}


LivenessAnalyzerBlock* LivenessAnalyzer::NewBlock() {
  LivenessAnalyzerBlock* result =
      new (zone()->New(sizeof(LivenessAnalyzerBlock))) LivenessAnalyzerBlock(
          blocks_.size(), local_count_, has_accumulator_, zone());
  blocks_.push_back(result);
  return result;
}


LivenessAnalyzerBlock* LivenessAnalyzer::NewBlock(
    LivenessAnalyzerBlock* predecessor) {
  LivenessAnalyzerBlock* result = NewBlock();
  result->AddPredecessor(predecessor);
  return result;
}


void LivenessAnalyzer::Queue(LivenessAnalyzerBlock* block) {
  if (!block->IsQueued()) {
    block->SetQueued();
    queue_.push(block);
  }
}


void LivenessAnalyzer::Run(NonLiveFrameStateSlotReplacer* replacer) {
  if (local_count_ == 0 && !has_accumulator_) {
    // No variables => nothing to do.
    return;
  }

  // Put all blocks into the queue.
  DCHECK(queue_.empty());
  for (auto block : blocks_) {
    Queue(block);
  }

  // Compute the fix-point.
  BitVector working_area(
      static_cast<int>(local_count_) + (has_accumulator_ ? 1 : 0), zone_);
  while (!queue_.empty()) {
    LivenessAnalyzerBlock* block = queue_.front();
    queue_.pop();
    block->Process(&working_area, nullptr);

    for (auto i = block->pred_begin(); i != block->pred_end(); i++) {
      if ((*i)->UpdateLive(&working_area)) {
        Queue(*i);
      }
    }
  }

  // Update the frame states according to the liveness.
  for (auto block : blocks_) {
    block->Process(&working_area, replacer);
  }
}

LivenessAnalyzerBlock::LivenessAnalyzerBlock(size_t id, size_t local_count,
                                             bool has_accumulator, Zone* zone)
    : entries_(zone),
      predecessors_(zone),
      live_(static_cast<int>(local_count) + (has_accumulator ? 1 : 0), zone),
      queued_(false),
      has_accumulator_(has_accumulator),
      id_(id) {}

void LivenessAnalyzerBlock::Process(BitVector* result,
                                    NonLiveFrameStateSlotReplacer* replacer) {
  queued_ = false;

  // Copy the bitvector to the target bit vector.
  result->CopyFrom(live_);

  for (auto entry : base::Reversed(entries_)) {
    switch (entry.kind()) {
      case Entry::kLookup:
        result->Add(entry.var());
        break;
      case Entry::kBind:
        result->Remove(entry.var());
        break;
      case Entry::kCheckpoint:
        if (replacer != nullptr) {
          replacer->ClearNonLiveFrameStateSlots(entry.node(), result);
        }
        break;
    }
  }
}


bool LivenessAnalyzerBlock::UpdateLive(BitVector* working_area) {
  return live_.UnionIsChanged(*working_area);
}


void NonLiveFrameStateSlotReplacer::ClearNonLiveFrameStateSlots(
    Node* frame_state, BitVector* liveness) {
  DCHECK_EQ(liveness->length(), permanently_live_.length());

  DCHECK_EQ(frame_state->opcode(), IrOpcode::kFrameState);
  Node* locals_state = frame_state->InputAt(1);
  DCHECK_EQ(locals_state->opcode(), IrOpcode::kStateValues);
  int count = liveness->length() - (has_accumulator_ ? 1 : 0);
  DCHECK_EQ(count, static_cast<int>(StateValuesAccess(locals_state).size()));
  for (int i = 0; i < count; i++) {
    if (!liveness->Contains(i) && !permanently_live_.Contains(i)) {
      Node* new_values = ClearNonLiveStateValues(locals_state, liveness);
      frame_state->ReplaceInput(1, new_values);
      break;
    }
  }

  if (has_accumulator_) {
    DCHECK_EQ(frame_state->InputAt(2)->opcode(), IrOpcode::kStateValues);
    DCHECK_EQ(
        static_cast<int>(StateValuesAccess(frame_state->InputAt(2)).size()), 1);
    int index = liveness->length() - 1;
    if (!liveness->Contains(index) && !permanently_live_.Contains(index)) {
      Node* new_value =
          state_values_cache()->GetNodeForValues(&replacement_node_, 1);
      frame_state->ReplaceInput(2, new_value);
    }
  }
}


Node* NonLiveFrameStateSlotReplacer::ClearNonLiveStateValues(
    Node* values, BitVector* liveness) {
  DCHECK(inputs_buffer_.empty());

  int var = 0;
  for (Node* value_node : values->inputs()) {
    // Make sure this isn't a state value tree
    DCHECK(value_node->opcode() != IrOpcode::kStateValues);

    // Index of the next variable is its furure index in the inputs buffer,
    // i.e., the buffer's size.
    bool live = liveness->Contains(var) || permanently_live_.Contains(var);
    inputs_buffer_.push_back(live ? value_node : replacement_node_);

    var++;
  }

  Node* result = state_values_cache()->GetNodeForValues(
      inputs_buffer_.empty() ? nullptr : &(inputs_buffer_.front()),
      inputs_buffer_.size());
  inputs_buffer_.clear();
  return result;
}


void LivenessAnalyzerBlock::Print(std::ostream& os) {
  os << "Block " << id();
  bool first = true;
  for (LivenessAnalyzerBlock* pred : predecessors_) {
    if (!first) {
      os << ", ";
    } else {
      os << "; predecessors: ";
      first = false;
    }
    os << pred->id();
  }
  os << std::endl;

  for (auto entry : entries_) {
    os << "    ";
    switch (entry.kind()) {
      case Entry::kLookup:
        if (has_accumulator_ && entry.var() == live_.length() - 1) {
          os << "- Lookup accumulator" << std::endl;
        } else {
          os << "- Lookup " << entry.var() << std::endl;
        }
        break;
      case Entry::kBind:
        if (has_accumulator_ && entry.var() == live_.length() - 1) {
          os << "- Bind accumulator" << std::endl;
        } else {
          os << "- Bind " << entry.var() << std::endl;
        }
        break;
      case Entry::kCheckpoint:
        os << "- Checkpoint " << entry.node()->id() << std::endl;
        break;
    }
  }

  if (live_.length() > 0) {
    os << "    Live set: ";
    for (int i = 0; i < live_.length(); i++) {
      os << (live_.Contains(i) ? "L" : ".");
    }
    os << std::endl;
  }
}

}  // namespace compiler
}  // namespace internal
}  // namespace v8