/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "execution_subgraph.h"

#include <algorithm>
#include <unordered_set>

#include "android-base/macros.h"
#include "base/arena_allocator.h"
#include "base/arena_bit_vector.h"
#include "base/globals.h"
#include "base/scoped_arena_allocator.h"
#include "nodes.h"

namespace art {

ExecutionSubgraph::ExecutionSubgraph(HGraph* graph, ScopedArenaAllocator* allocator)
    : graph_(graph),
      allocator_(allocator),
      allowed_successors_(graph_->GetBlocks().size(),
                          ~(std::bitset<kMaxFilterableSuccessors> {}),
                          allocator_->Adapter(kArenaAllocLSA)),
      unreachable_blocks_(
          allocator_, graph_->GetBlocks().size(), /*expandable=*/ false, kArenaAllocLSA),
      valid_(true),
      needs_prune_(false),
      finalized_(false) {
  if (valid_) {
    DCHECK(std::all_of(graph->GetBlocks().begin(), graph->GetBlocks().end(), [](HBasicBlock* it) {
      return it == nullptr || it->GetSuccessors().size() <= kMaxFilterableSuccessors;
    }));
  }
}

void ExecutionSubgraph::RemoveBlock(const HBasicBlock* to_remove) {
  if (!valid_) {
    return;
  }
  uint32_t id = to_remove->GetBlockId();
  if (unreachable_blocks_.IsBitSet(id)) {
    if (kIsDebugBuild) {
      // This isn't really needed but it's good to have this so it functions as
      // a DCHECK that we always call Prune after removing any block.
      needs_prune_ = true;
    }
    return;
  }
  unreachable_blocks_.SetBit(id);
  for (HBasicBlock* pred : to_remove->GetPredecessors()) {
    std::bitset<kMaxFilterableSuccessors> allowed_successors {};
    // ZipCount iterates over both the successors and the index of them at the same time.
    for (auto [succ, i] : ZipCount(MakeIterationRange(pred->GetSuccessors()))) {
      if (succ != to_remove) {
        allowed_successors.set(i);
      }
    }
    LimitBlockSuccessors(pred, allowed_successors);
  }
}

// Removes sink nodes.
void ExecutionSubgraph::Prune() {
  if (UNLIKELY(!valid_)) {
    return;
  }
  needs_prune_ = false;
  // This is the record of the edges that were both (1) explored and (2) reached
  // the exit node.
  {
    // Allocator for temporary values.
    ScopedArenaAllocator temporaries(graph_->GetArenaStack());
    ScopedArenaVector<std::bitset<kMaxFilterableSuccessors>> results(
        graph_->GetBlocks().size(), temporaries.Adapter(kArenaAllocLSA));
    unreachable_blocks_.ClearAllBits();
    // TODO We should support infinite loops as well.
    if (UNLIKELY(graph_->GetExitBlock() == nullptr)) {
      // Infinite loop
      valid_ = false;
      return;
    }
    // Fills up the 'results' map with what we need to add to update
    // allowed_successors in order to prune sink nodes.
    bool start_reaches_end = false;
    // This is basically a DFS of the graph with some edges skipped.
    {
      const size_t num_blocks = graph_->GetBlocks().size();
      constexpr ssize_t kUnvisitedSuccIdx = -1;
      ArenaBitVector visiting(&temporaries, num_blocks, false, kArenaAllocLSA);
      // How many of the successors of each block we have already examined. This
      // has three states.
      // (1) kUnvisitedSuccIdx: we have not examined any edges,
      // (2) 0 <= val < # of successors: we have examined 'val' successors/are
      // currently examining successors_[val],
      // (3) kMaxFilterableSuccessors: We have examined all of the successors of
      // the block (the 'result' is final).
      ScopedArenaVector<ssize_t> last_succ_seen(
          num_blocks, kUnvisitedSuccIdx, temporaries.Adapter(kArenaAllocLSA));
      // A stack of which blocks we are visiting in this DFS traversal. Does not
      // include the current-block. Used with last_succ_seen to figure out which
      // bits to set if we find a path to the end/loop.
      ScopedArenaVector<uint32_t> current_path(temporaries.Adapter(kArenaAllocLSA));
      // Just ensure we have enough space. The allocator will be cleared shortly
      // anyway so this is fast.
      current_path.reserve(num_blocks);
      // Current block we are examining. Modified only by 'push_block' and 'pop_block'
      const HBasicBlock* cur_block = graph_->GetEntryBlock();
      // Used to note a recur where we will start iterating on 'blk' and save
      // where we are. We must 'continue' immediately after this.
      auto push_block = [&](const HBasicBlock* blk) {
        DCHECK(std::find(current_path.cbegin(), current_path.cend(), cur_block->GetBlockId()) ==
               current_path.end());
        if (kIsDebugBuild) {
          std::for_each(current_path.cbegin(), current_path.cend(), [&](auto id) {
            DCHECK_GT(last_succ_seen[id], kUnvisitedSuccIdx) << id;
            DCHECK_LT(last_succ_seen[id], static_cast<ssize_t>(kMaxFilterableSuccessors)) << id;
          });
        }
        current_path.push_back(cur_block->GetBlockId());
        visiting.SetBit(cur_block->GetBlockId());
        cur_block = blk;
      };
      // Used to note that we have fully explored a block and should return back
      // up. Sets cur_block appropriately. We must 'continue' immediately after
      // calling this.
      auto pop_block = [&]() {
        if (UNLIKELY(current_path.empty())) {
          // Should only happen if entry-blocks successors are exhausted.
          DCHECK_GE(last_succ_seen[graph_->GetEntryBlock()->GetBlockId()],
                    static_cast<ssize_t>(graph_->GetEntryBlock()->GetSuccessors().size()));
          cur_block = nullptr;
        } else {
          const HBasicBlock* last = graph_->GetBlocks()[current_path.back()];
          visiting.ClearBit(current_path.back());
          current_path.pop_back();
          cur_block = last;
        }
      };
      // Mark the current path as a path to the end. This is in contrast to paths
      // that end in (eg) removed blocks.
      auto propagate_true = [&]() {
        for (uint32_t id : current_path) {
          DCHECK_GT(last_succ_seen[id], kUnvisitedSuccIdx);
          DCHECK_LT(last_succ_seen[id], static_cast<ssize_t>(kMaxFilterableSuccessors));
          results[id].set(last_succ_seen[id]);
        }
      };
      ssize_t num_entry_succ = graph_->GetEntryBlock()->GetSuccessors().size();
      // As long as the entry-block has not explored all successors we still have
      // work to do.
      const uint32_t entry_block_id = graph_->GetEntryBlock()->GetBlockId();
      while (num_entry_succ > last_succ_seen[entry_block_id]) {
        DCHECK(cur_block != nullptr);
        uint32_t id = cur_block->GetBlockId();
        DCHECK((current_path.empty() && cur_block == graph_->GetEntryBlock()) ||
               current_path.front() == graph_->GetEntryBlock()->GetBlockId())
            << "current path size: " << current_path.size()
            << " cur_block id: " << cur_block->GetBlockId() << " entry id "
            << graph_->GetEntryBlock()->GetBlockId();
        DCHECK(!visiting.IsBitSet(id))
            << "Somehow ended up in a loop! This should have been caught before now! " << id;
        std::bitset<kMaxFilterableSuccessors>& result = results[id];
        if (cur_block == graph_->GetExitBlock()) {
          start_reaches_end = true;
          propagate_true();
          pop_block();
          continue;
        } else if (last_succ_seen[id] == kMaxFilterableSuccessors) {
          // Already fully explored.
          if (result.any()) {
            propagate_true();
          }
          pop_block();
          continue;
        }
        // NB This is a pointer. Modifications modify the last_succ_seen.
        ssize_t* cur_succ = &last_succ_seen[id];
        std::bitset<kMaxFilterableSuccessors> succ_bitmap = GetAllowedSuccessors(cur_block);
        // Get next successor allowed.
        while (++(*cur_succ) < static_cast<ssize_t>(kMaxFilterableSuccessors) &&
               !succ_bitmap.test(*cur_succ)) {
          DCHECK_GE(*cur_succ, 0);
        }
        if (*cur_succ >= static_cast<ssize_t>(cur_block->GetSuccessors().size())) {
          // No more successors. Mark that we've checked everything. Later visits
          // to this node can use the existing data.
          DCHECK_LE(*cur_succ, static_cast<ssize_t>(kMaxFilterableSuccessors));
          *cur_succ = kMaxFilterableSuccessors;
          pop_block();
          continue;
        }
        const HBasicBlock* nxt = cur_block->GetSuccessors()[*cur_succ];
        DCHECK(nxt != nullptr) << "id: " << *cur_succ
                               << " max: " << cur_block->GetSuccessors().size();
        if (visiting.IsBitSet(nxt->GetBlockId())) {
          // This is a loop. Mark it and continue on. Mark allowed-successor on
          // this block's results as well.
          result.set(*cur_succ);
          propagate_true();
        } else {
          // Not a loop yet. Recur.
          push_block(nxt);
        }
      }
    }
    // If we can't reach the end then there is no path through the graph without
    // hitting excluded blocks
    if (UNLIKELY(!start_reaches_end)) {
      valid_ = false;
      return;
    }
    // Mark blocks we didn't see in the ReachesEnd flood-fill
    for (const HBasicBlock* blk : graph_->GetBlocks()) {
      if (blk != nullptr &&
          results[blk->GetBlockId()].none() &&
          blk != graph_->GetExitBlock() &&
          blk != graph_->GetEntryBlock()) {
        // We never visited this block, must be unreachable.
        unreachable_blocks_.SetBit(blk->GetBlockId());
      }
    }
    // write the new data.
    memcpy(allowed_successors_.data(),
           results.data(),
           results.size() * sizeof(std::bitset<kMaxFilterableSuccessors>));
  }
  RecalculateExcludedCohort();
}

void ExecutionSubgraph::RemoveConcavity() {
  if (UNLIKELY(!valid_)) {
    return;
  }
  DCHECK(!needs_prune_);
  for (const HBasicBlock* blk : graph_->GetBlocks()) {
    if (blk == nullptr || unreachable_blocks_.IsBitSet(blk->GetBlockId())) {
      continue;
    }
    uint32_t blkid = blk->GetBlockId();
    if (std::any_of(unreachable_blocks_.Indexes().begin(),
                    unreachable_blocks_.Indexes().end(),
                    [&](uint32_t skipped) { return graph_->PathBetween(skipped, blkid); }) &&
        std::any_of(unreachable_blocks_.Indexes().begin(),
                    unreachable_blocks_.Indexes().end(),
                    [&](uint32_t skipped) { return graph_->PathBetween(blkid, skipped); })) {
      RemoveBlock(blk);
    }
  }
  Prune();
}

void ExecutionSubgraph::RecalculateExcludedCohort() {
  DCHECK(!needs_prune_);
  excluded_list_.emplace(allocator_->Adapter(kArenaAllocLSA));
  ScopedArenaVector<ExcludedCohort>& res = excluded_list_.value();
  // Make a copy of unreachable_blocks_;
  ArenaBitVector unreachable(allocator_, graph_->GetBlocks().size(), false, kArenaAllocLSA);
  unreachable.Copy(&unreachable_blocks_);
  // Split cohorts with union-find
  while (unreachable.IsAnyBitSet()) {
    res.emplace_back(allocator_, graph_);
    ExcludedCohort& cohort = res.back();
    // We don't allocate except for the queue beyond here so create another arena to save memory.
    ScopedArenaAllocator alloc(graph_->GetArenaStack());
    ScopedArenaQueue<const HBasicBlock*> worklist(alloc.Adapter(kArenaAllocLSA));
    // Select an arbitrary node
    const HBasicBlock* first = graph_->GetBlocks()[unreachable.GetHighestBitSet()];
    worklist.push(first);
    do {
      // Flood-fill both forwards and backwards.
      const HBasicBlock* cur = worklist.front();
      worklist.pop();
      if (!unreachable.IsBitSet(cur->GetBlockId())) {
        // Already visited or reachable somewhere else.
        continue;
      }
      unreachable.ClearBit(cur->GetBlockId());
      cohort.blocks_.SetBit(cur->GetBlockId());
      // don't bother filtering here, it's done next go-around
      for (const HBasicBlock* pred : cur->GetPredecessors()) {
        worklist.push(pred);
      }
      for (const HBasicBlock* succ : cur->GetSuccessors()) {
        worklist.push(succ);
      }
    } while (!worklist.empty());
  }
  // Figure out entry & exit nodes.
  for (ExcludedCohort& cohort : res) {
    DCHECK(cohort.blocks_.IsAnyBitSet());
    auto is_external = [&](const HBasicBlock* ext) -> bool {
      return !cohort.blocks_.IsBitSet(ext->GetBlockId());
    };
    for (const HBasicBlock* blk : cohort.Blocks()) {
      const auto& preds = blk->GetPredecessors();
      const auto& succs = blk->GetSuccessors();
      if (std::any_of(preds.cbegin(), preds.cend(), is_external)) {
        cohort.entry_blocks_.SetBit(blk->GetBlockId());
      }
      if (std::any_of(succs.cbegin(), succs.cend(), is_external)) {
        cohort.exit_blocks_.SetBit(blk->GetBlockId());
      }
    }
  }
}

std::ostream& operator<<(std::ostream& os, const ExecutionSubgraph::ExcludedCohort& ex) {
  ex.Dump(os);
  return os;
}

void ExecutionSubgraph::ExcludedCohort::Dump(std::ostream& os) const {
  auto dump = [&](BitVecBlockRange arr) {
    os << "[";
    bool first = true;
    for (const HBasicBlock* b : arr) {
      if (!first) {
        os << ", ";
      }
      first = false;
      os << b->GetBlockId();
    }
    os << "]";
  };
  auto dump_blocks = [&]() {
    os << "[";
    bool first = true;
    for (const HBasicBlock* b : Blocks()) {
      if (!entry_blocks_.IsBitSet(b->GetBlockId()) && !exit_blocks_.IsBitSet(b->GetBlockId())) {
        if (!first) {
          os << ", ";
        }
        first = false;
        os << b->GetBlockId();
      }
    }
    os << "]";
  };

  os << "{ entry: ";
  dump(EntryBlocks());
  os << ", interior: ";
  dump_blocks();
  os << ", exit: ";
  dump(ExitBlocks());
  os << "}";
}

}  // namespace art
