/*
 * Copyright 2016 WebAssembly Community Group participants
 *
 * 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 "Relooper.h"

#include <stdlib.h>
#include <string.h>

#include <list>
#include <stack>
#include <string>

#include "ir/branch-utils.h"
#include "ir/utils.h"
#include "parsing.h"

namespace CFG {

template<class T, class U>
static bool contains(const T& container, const U& contained) {
  return !!container.count(contained);
}

#ifdef RELOOPER_DEBUG
static void PrintDebug(const char* Format, ...);
#define DebugDump(x, ...) Debugging::Dump(x, __VA_ARGS__)
#else
#define PrintDebug(x, ...)
#define DebugDump(x, ...)
#endif

// Rendering utilities

static wasm::Expression* HandleFollowupMultiples(wasm::Expression* Ret,
                                                 Shape* Parent,
                                                 RelooperBuilder& Builder,
                                                 bool InLoop) {
  if (!Parent->Next) {
    return Ret;
  }

  auto* Curr = Ret->dynCast<wasm::Block>();
  if (!Curr || Curr->name.is()) {
    Curr = Builder.makeBlock(Ret);
  }
  // for each multiple after us, we create a block target for breaks to reach
  while (Parent->Next) {
    auto* Multiple = Shape::IsMultiple(Parent->Next);
    if (!Multiple) {
      break;
    }
    for (auto& [Id, Body] : Multiple->InnerMap) {
      Curr->name = Builder.getBlockBreakName(Id);
      Curr->finalize(); // it may now be reachable, via a break
      auto* Outer = Builder.makeBlock(Curr);
      Outer->list.push_back(Body->Render(Builder, InLoop));
      Outer->finalize(); // TODO: not really necessary
      Curr = Outer;
    }
    Parent->Next = Parent->Next->Next;
  }
  // after the multiples is a simple or a loop, in both cases we must hit an
  // entry block, and so this is the last one we need to take into account now
  // (this is why we require that loops hit an entry).
  if (Parent->Next) {
    auto* Simple = Shape::IsSimple(Parent->Next);
    if (Simple) {
      // breaking on the next block's id takes us out, where we
      // will reach its rendering
      Curr->name = Builder.getBlockBreakName(Simple->Inner->Id);
    } else {
      // add one break target per entry for the loop
      auto* Loop = Shape::IsLoop(Parent->Next);
      assert(Loop);
      assert(Loop->Entries.size() > 0);
      if (Loop->Entries.size() == 1) {
        Curr->name = Builder.getBlockBreakName((*Loop->Entries.begin())->Id);
      } else {
        for (auto* Entry : Loop->Entries) {
          Curr->name = Builder.getBlockBreakName(Entry->Id);
          Curr->finalize();
          auto* Outer = Builder.makeBlock(Curr);
          Outer->finalize(); // TODO: not really necessary
          Curr = Outer;
        }
      }
    }
  }
  Curr->finalize();
  return Curr;
}

// Branch

Branch::Branch(wasm::Expression* ConditionInit, wasm::Expression* CodeInit)
  : Condition(ConditionInit), Code(CodeInit) {}

Branch::Branch(std::vector<wasm::Index>&& ValuesInit,
               wasm::Expression* CodeInit)
  : Condition(nullptr), Code(CodeInit) {
  if (ValuesInit.size() > 0) {
    SwitchValues = std::make_unique<std::vector<wasm::Index>>(ValuesInit);
  }
  // otherwise, it is the default
}

wasm::Expression*
Branch::Render(RelooperBuilder& Builder, Block* Target, bool SetLabel) {
  auto* Ret = Builder.makeBlock();
  if (Code) {
    Ret->list.push_back(Code);
  }
  if (SetLabel) {
    Ret->list.push_back(Builder.makeSetLabel(Target->Id));
  }
  if (Type == Break) {
    Ret->list.push_back(Builder.makeBlockBreak(Target->Id));
  } else if (Type == Continue) {
    assert(Ancestor);
    Ret->list.push_back(Builder.makeShapeContinue(Ancestor->Id));
  }
  Ret->finalize();
  return Ret;
}

// Block

Block::Block(Relooper* relooper,
             wasm::Expression* CodeInit,
             wasm::Expression* SwitchConditionInit)
  : relooper(relooper), Code(CodeInit), SwitchCondition(SwitchConditionInit),
    IsCheckedMultipleEntry(false) {}

void Block::AddBranchTo(Block* Target,
                        wasm::Expression* Condition,
                        wasm::Expression* Code) {
  // cannot add more than one branch to the same target
  assert(!contains(BranchesOut, Target));
  BranchesOut[Target] = relooper->AddBranch(Condition, Code);
}

void Block::AddSwitchBranchTo(Block* Target,
                              std::vector<wasm::Index>&& Values,
                              wasm::Expression* Code) {
  // cannot add more than one branch to the same target
  assert(!contains(BranchesOut, Target));
  BranchesOut[Target] = relooper->AddBranch(std::move(Values), Code);
}

wasm::Expression* Block::Render(RelooperBuilder& Builder, bool InLoop) {
  auto* Ret = Builder.makeBlock();
  if (IsCheckedMultipleEntry && InLoop) {
    Ret->list.push_back(Builder.makeSetLabel(0));
  }
  if (Code) {
    Ret->list.push_back(Code);
  }

  if (!ProcessedBranchesOut.size()) {
    Ret->finalize();
    return Ret;
  }

  // in some cases it is clear we can avoid setting label, see later
  bool SetLabel = true;

  // A setting of the label variable (label = x) is necessary if it can
  // cause an impact. The main case is where we set label to x, then elsewhere
  // we check if label is equal to that value, i.e., that label is an entry
  // in a multiple block. We also need to reset the label when we enter
  // that block, so that each setting is a one-time action: consider
  //
  //    while (1) {
  //      if (check) label = 1;
  //      if (label == 1) { label = 0 }
  //    }
  //
  // (Note that this case is impossible due to fusing, but that is not
  // material here.) So setting to 0 is important just to clear the 1 for
  // future iterations.
  // TODO: When inside a loop, if necessary clear the label variable
  //       once on the top, and never do settings that are in effect clears

  // Fusing: If the next is a Multiple, we can fuse it with this block. Note
  // that we must be the Inner of a Simple, so fusing means joining a Simple
  // to a Multiple. What happens there is that all options in the Multiple
  // *must* appear in the Simple (the Simple is the only one reaching the
  // Multiple), so we can remove the Multiple and add its independent groups
  // into the Simple's branches.
  MultipleShape* Fused = Shape::IsMultiple(Parent->Next);
  if (Fused) {
    PrintDebug("Fusing Multiple to Simple\n", 0);
    Parent->Next = Parent->Next->Next;
    // When the Multiple has the same number of groups as we have branches,
    // they will all be fused, so it is safe to not set the label at all.
    // If a switch, then we can have multiple branches to the same target
    // (in different table indexes), and so this check is not sufficient
    // TODO: optimize
    if (SetLabel && Fused->InnerMap.size() == ProcessedBranchesOut.size() &&
        !SwitchCondition) {
      SetLabel = false;
    }
  }

  // The block we branch to without checking the condition, if none of the other
  // conditions held.
  Block* DefaultTarget = nullptr;

  // Find the default target, the one without a condition
  for (auto& [Target, Details] : ProcessedBranchesOut) {
    if ((!SwitchCondition && !Details->Condition) ||
        (SwitchCondition && !Details->SwitchValues)) {
      assert(!DefaultTarget &&
             "block has branches without a default (nullptr for the "
             "condition)"); // Must be exactly one default // nullptr
      DefaultTarget = Target;
    }
  }
  // Since each Block* must* branch somewhere, this must be set
  assert(DefaultTarget);

  // root of the main part, that we are about to emit
  wasm::Expression* Root = nullptr;

  if (!SwitchCondition) {
    // We'll emit a chain of if-elses
    wasm::If* CurrIf = nullptr;

    // we build an if, then add a child, then add a child to that, etc., so we
    // must finalize them in reverse order
    std::vector<wasm::If*> finalizeStack;

    wasm::Expression* RemainingConditions = nullptr;

    for (auto iter = ProcessedBranchesOut.begin();; iter++) {
      Block* Target;
      Branch* Details;
      if (iter != ProcessedBranchesOut.end()) {
        Target = iter->first;
        if (Target == DefaultTarget) {
          continue; // done at the end
        }
        Details = iter->second;
        // must have a condition if this is not the default target
        assert(Details->Condition);
      } else {
        Target = DefaultTarget;
        Details = ProcessedBranchesOut[DefaultTarget];
      }
      bool SetCurrLabel = SetLabel && Target->IsCheckedMultipleEntry;
      bool HasFusedContent = Fused && contains(Fused->InnerMap, Target->Id);
      if (HasFusedContent) {
        assert(Details->Type == Branch::Break);
        Details->Type = Branch::Direct;
      }
      wasm::Expression* CurrContent = nullptr;
      bool IsDefault = iter == ProcessedBranchesOut.end();
      if (SetCurrLabel || Details->Type != Branch::Direct || HasFusedContent ||
          Details->Code) {
        CurrContent = Details->Render(Builder, Target, SetCurrLabel);
        if (HasFusedContent) {
          CurrContent = Builder.blockify(
            CurrContent,
            Fused->InnerMap.find(Target->Id)->second->Render(Builder, InLoop));
        }
      }
      // If there is nothing to show in this branch, omit the condition
      if (CurrContent) {
        if (IsDefault) {
          wasm::Expression* Now;
          if (RemainingConditions) {
            Now = Builder.makeIf(RemainingConditions, CurrContent);
            finalizeStack.push_back(Now->cast<wasm::If>());
          } else {
            Now = CurrContent;
          }
          if (!CurrIf) {
            assert(!Root);
            Root = Now;
          } else {
            CurrIf->ifFalse = Now;
            CurrIf->finalize();
          }
        } else {
          auto* Now = Builder.makeIf(Details->Condition, CurrContent);
          finalizeStack.push_back(Now);
          if (!CurrIf) {
            assert(!Root);
            Root = CurrIf = Now;
          } else {
            CurrIf->ifFalse = Now;
            CurrIf->finalize();
            CurrIf = Now;
          }
        }
      } else {
        auto* Now = Builder.makeUnary(wasm::EqZInt32, Details->Condition);
        if (RemainingConditions) {
          RemainingConditions =
            Builder.makeBinary(wasm::AndInt32, RemainingConditions, Now);
        } else {
          RemainingConditions = Now;
        }
      }
      if (IsDefault) {
        break;
      }
    }

    // finalize the if-chains
    while (finalizeStack.size() > 0) {
      wasm::If* curr = finalizeStack.back();
      finalizeStack.pop_back();
      curr->finalize();
    }

  } else {
    // Emit a switch
    auto Base = std::string("switch$") + std::to_string(Id);
    auto SwitchDefault = wasm::Name(Base + "$default");
    auto SwitchLeave = wasm::Name(Base + "$leave");
    std::map<Block*, wasm::Name> BlockNameMap;
    auto* Outer = Builder.makeBlock();
    auto* Inner = Outer;
    std::vector<wasm::Name> Table;
    for (auto& [Target, Details] : ProcessedBranchesOut) {
      wasm::Name CurrName;
      if (Details->SwitchValues) {
        CurrName = wasm::Name(Base + "$case$" + std::to_string(Target->Id));
      } else {
        CurrName = SwitchDefault;
      }
      // generate the content for this block
      bool SetCurrLabel = SetLabel && Target->IsCheckedMultipleEntry;
      bool HasFusedContent = Fused && contains(Fused->InnerMap, Target->Id);
      if (HasFusedContent) {
        assert(Details->Type == Branch::Break);
        Details->Type = Branch::Direct;
      }
      wasm::Expression* CurrContent = nullptr;
      if (SetCurrLabel || Details->Type != Branch::Direct || HasFusedContent ||
          Details->Code) {
        CurrContent = Details->Render(Builder, Target, SetCurrLabel);
        if (HasFusedContent) {
          CurrContent = Builder.blockify(
            CurrContent,
            Fused->InnerMap.find(Target->Id)->second->Render(Builder, InLoop));
        }
      }
      // generate a block to branch to, if we have content
      if (CurrContent) {
        auto* NextOuter = Builder.makeBlock();
        NextOuter->list.push_back(Outer);
        // breaking on Outer leads to the content in NextOuter
        Outer->name = CurrName;
        NextOuter->list.push_back(CurrContent);
        // if this is not a dead end, also need to break to the outside this is
        // both an optimization, and avoids incorrectness as adding a break in
        // unreachable code can make a place look reachable that isn't
        if (CurrContent->type != wasm::Type::unreachable) {
          NextOuter->list.push_back(Builder.makeBreak(SwitchLeave));
        }
        // prepare for more nesting
        Outer = NextOuter;
      } else {
        CurrName = SwitchLeave; // just go out straight from the table
        if (!Details->SwitchValues) {
          // this is the default, and it has no content. So make the default be
          // the leave
          for (auto& Value : Table) {
            if (Value == SwitchDefault) {
              Value = SwitchLeave;
            }
          }
          SwitchDefault = SwitchLeave;
        }
      }
      if (Details->SwitchValues) {
        for (auto Value : *Details->SwitchValues) {
          while (Table.size() <= Value) {
            Table.push_back(SwitchDefault);
          }
          Table[Value] = CurrName;
        }
      }
    }
    // finish up the whole pattern
    Outer->name = SwitchLeave;
    Inner->list.push_back(
      Builder.makeSwitch(Table, SwitchDefault, SwitchCondition));
    Root = Outer;
  }

  if (Root) {
    Ret->list.push_back(Root);
  }
  Ret->finalize();

  return Ret;
}

// SimpleShape

wasm::Expression* SimpleShape::Render(RelooperBuilder& Builder, bool InLoop) {
  auto* Ret = Inner->Render(Builder, InLoop);
  Ret = HandleFollowupMultiples(Ret, this, Builder, InLoop);
  if (Next) {
    Ret = Builder.makeSequence(Ret, Next->Render(Builder, InLoop));
  }
  return Ret;
}

// MultipleShape

wasm::Expression* MultipleShape::Render(RelooperBuilder& Builder, bool InLoop) {
  // TODO: consider switch
  // emit an if-else chain
  wasm::If* FirstIf = nullptr;
  wasm::If* CurrIf = nullptr;
  std::vector<wasm::If*> finalizeStack;
  for (auto& [Id, Body] : InnerMap) {
    auto* Now =
      Builder.makeIf(Builder.makeCheckLabel(Id), Body->Render(Builder, InLoop));
    finalizeStack.push_back(Now);
    if (!CurrIf) {
      FirstIf = CurrIf = Now;
    } else {
      CurrIf->ifFalse = Now;
      CurrIf->finalize();
      CurrIf = Now;
    }
  }
  while (finalizeStack.size() > 0) {
    wasm::If* curr = finalizeStack.back();
    finalizeStack.pop_back();
    curr->finalize();
  }
  wasm::Expression* Ret = Builder.makeBlock(FirstIf);
  Ret = HandleFollowupMultiples(Ret, this, Builder, InLoop);
  if (Next) {
    Ret = Builder.makeSequence(Ret, Next->Render(Builder, InLoop));
  }
  return Ret;
}

// LoopShape

wasm::Expression* LoopShape::Render(RelooperBuilder& Builder, bool InLoop) {
  wasm::Expression* Ret = Builder.makeLoop(Builder.getShapeContinueName(Id),
                                           Inner->Render(Builder, true));
  Ret = HandleFollowupMultiples(Ret, this, Builder, InLoop);
  if (Next) {
    Ret = Builder.makeSequence(Ret, Next->Render(Builder, InLoop));
  }
  return Ret;
}

// Relooper

Relooper::Relooper(wasm::Module* ModuleInit)
  : Module(ModuleInit), Root(nullptr), MinSize(false), BlockIdCounter(1),
    ShapeIdCounter(0) { // block ID 0 is reserved for clearings
}

Block* Relooper::AddBlock(wasm::Expression* CodeInit,
                          wasm::Expression* SwitchConditionInit) {

  auto block = std::make_unique<Block>(this, CodeInit, SwitchConditionInit);
  block->Id = BlockIdCounter++;
  auto* blockPtr = block.get();
  Blocks.push_back(std::move(block));
  return blockPtr;
}

Branch* Relooper::AddBranch(wasm::Expression* ConditionInit,
                            wasm::Expression* CodeInit) {
  auto branch = std::make_unique<Branch>(ConditionInit, CodeInit);
  auto* branchPtr = branch.get();
  Branches.push_back(std::move(branch));
  return branchPtr;
}
Branch* Relooper::AddBranch(std::vector<wasm::Index>&& ValuesInit,
                            wasm::Expression* CodeInit) {
  auto branch = std::make_unique<Branch>(std::move(ValuesInit), CodeInit);
  auto* branchPtr = branch.get();
  Branches.push_back(std::move(branch));
  return branchPtr;
}

SimpleShape* Relooper::AddSimpleShape() {
  auto shape = std::make_unique<SimpleShape>();
  shape->Id = ShapeIdCounter++;
  auto* shapePtr = shape.get();
  Shapes.push_back(std::move(shape));
  return shapePtr;
}

MultipleShape* Relooper::AddMultipleShape() {
  auto shape = std::make_unique<MultipleShape>();
  shape->Id = ShapeIdCounter++;
  auto* shapePtr = shape.get();
  Shapes.push_back(std::move(shape));
  return shapePtr;
}

LoopShape* Relooper::AddLoopShape() {
  auto shape = std::make_unique<LoopShape>();
  shape->Id = ShapeIdCounter++;
  auto* shapePtr = shape.get();
  Shapes.push_back(std::move(shape));
  return shapePtr;
}

namespace {

using BlockList = std::list<Block*>;

struct RelooperRecursor {
  Relooper* Parent;
  RelooperRecursor(Relooper* ParentInit) : Parent(ParentInit) {}
};

struct Liveness : public RelooperRecursor {
  Liveness(Relooper* Parent) : RelooperRecursor(Parent) {}
  BlockSet Live;

  void FindLive(Block* Root) {
    BlockList ToInvestigate;
    ToInvestigate.push_back(Root);
    while (ToInvestigate.size() > 0) {
      Block* Curr = ToInvestigate.front();
      ToInvestigate.pop_front();
      if (contains(Live, Curr)) {
        continue;
      }
      Live.insert(Curr);
      for (auto& iter : Curr->BranchesOut) {
        ToInvestigate.push_back(iter.first);
      }
    }
  }
};

using BranchBlock = std::pair<Branch*, Block*>;

struct Optimizer : public RelooperRecursor {
  Block* Entry;

  Optimizer(Relooper* Parent, Block* EntryInit)
    : RelooperRecursor(Parent), Entry(EntryInit) {
    // TODO: there are likely some rare but possible O(N^2) cases with this
    // looping
    bool More = true;
#if RELOOPER_OPTIMIZER_DEBUG
    std::cout << "pre-optimize\n";
    for (auto* Block : Parent->Blocks) {
      DebugDump(Block, "pre-block");
    }
#endif

    // First, run one-time preparatory passes.
    CanonicalizeCode();

    // Loop over passes that allow further reduction.
    while (More) {
      More = false;
      More = SkipEmptyBlocks() || More;
      More = MergeEquivalentBranches() || More;
      More = UnSwitch() || More;
      More = MergeConsecutiveBlocks() || More;
      // TODO: Merge identical blocks. This would avoid taking into account
      // their position / how they are reached, which means that the merging may
      // add overhead, so we do it carefully:
      //  * Merging a large-enough block is good for size, and we do it
      //    in we are in MinSize mode, which means we can tolerate slightly
      //    slower throughput.
      // TODO: Fuse a non-empty block with a single successor.
    }

    // Finally, run one-time final passes.
    // TODO

#if RELOOPER_OPTIMIZER_DEBUG
    std::cout << "post-optimize\n";
    for (auto* Block : Parent->Blocks) {
      DebugDump(Block, "post-block");
    }
#endif
  }

  // We will be performing code comparisons, so do some basic canonicalization
  // to avoid things being unequal for silly reasons.
  void CanonicalizeCode() {
    for (auto& Block : Parent->Blocks) {
      Block->Code = Canonicalize(Block->Code);
      for (auto& [_, Branch] : Block->BranchesOut) {
        if (Branch->Code) {
          Branch->Code = Canonicalize(Branch->Code);
        }
      }
    }
  }

  // If a branch goes to an empty block which has one target,
  // and there is no phi or switch to worry us, just skip through.
  bool SkipEmptyBlocks() {
    bool Worked = false;
    for (auto& CurrBlock : Parent->Blocks) {
      // Generate a new set of branches out TODO optimize
      BlockBranchMap NewBranchesOut;
      for (auto& iter : CurrBlock->BranchesOut) {
        auto* Next = iter.first;
        auto* NextBranch = iter.second;
        auto* First = Next;
        auto* Replacement = First;
#if RELOOPER_OPTIMIZER_DEBUG
        std::cout << " maybeskip from " << Block->Id << " to next=" << Next->Id
                  << '\n';
#endif
        std::unordered_set<decltype(Replacement)> Seen;
        while (1) {
          if (IsEmpty(Next) && Next->BranchesOut.size() == 1) {
            auto iter = Next->BranchesOut.begin();
            Block* NextNext = iter->first;
            Branch* NextNextBranch = iter->second;
            assert(!NextNextBranch->Condition && !NextNextBranch->SwitchValues);
            if (!NextNextBranch->Code) { // TODO: handle extra code too
              // We can skip through!
              Next = Replacement = NextNext;
              // If we've already seen this, stop - it's an infinite loop of
              // empty blocks we can skip through.
              if (!Seen.emplace(Replacement).second) {
                // Stop here. Note that if we started from X and ended up with X
                // once more, then Replacement == First and so lower down we
                // will not report that we did any work, avoiding an infinite
                // loop due to always thinking there is more work to do.
                break;
              } else {
                // Otherwise, keep going.
                continue;
              }
            }
          }
          break;
        }
        if (Replacement != First) {
#if RELOOPER_OPTIMIZER_DEBUG
          std::cout << "  skip to replacement! " << CurrBlock->Id << " -> "
                    << First->Id << " -> " << Replacement->Id << '\n';
#endif
          Worked = true;
        }
        // Add a branch to the target (which may be the unchanged original) in
        // the set of new branches. If it's a replacement, it may collide, and
        // we need to merge.
        if (NewBranchesOut.count(Replacement)) {
#if RELOOPER_OPTIMIZER_DEBUG
          std::cout << "  merge\n";
#endif
          MergeBranchInto(NextBranch, NewBranchesOut[Replacement]);
        } else {
          NewBranchesOut[Replacement] = NextBranch;
        }
      }
      CurrBlock->BranchesOut.swap(NewBranchesOut);
    }
    return Worked;
  }

  // Our IR has one Branch from each block to one of its targets, so there
  // is nothing to reduce there, but different targets may in fact be
  // equivalent in their *contents*.
  bool MergeEquivalentBranches() {
    bool Worked = false;
    for (auto& ParentBlock : Parent->Blocks) {
#if RELOOPER_OPTIMIZER_DEBUG
      std::cout << "at parent " << ParentBlock->Id << '\n';
#endif
      if (ParentBlock->BranchesOut.size() >= 2) {
        std::unordered_map<size_t, std::vector<BranchBlock>> HashedBranchesOut;
        std::vector<Block*> BlocksToErase;
        for (auto& [CurrBlock, CurrBranch] : ParentBlock->BranchesOut) {
#if RELOOPER_OPTIMIZER_DEBUG
          std::cout << "  consider child " << CurrBlock->Id << '\n';
#endif
          if (CurrBranch->Code) {
            // We can't merge code; ignore
            continue;
          }
          auto HashValue = Hash(CurrBlock);
          auto& HashedSiblings = HashedBranchesOut[HashValue];
          // Check if we are equivalent to any of them - if so, merge us.
          bool Merged = false;
          for (auto& [SiblingBranch, SiblingBlock] : HashedSiblings) {
            if (HaveEquivalentContents(CurrBlock, SiblingBlock)) {
#if RELOOPER_OPTIMIZER_DEBUG
              std::cout << "    equiv! to " << SiblingBlock->Id << '\n';
#endif
              MergeBranchInto(CurrBranch, SiblingBranch);
              BlocksToErase.push_back(CurrBlock);
              Merged = true;
              Worked = true;
            }
#if RELOOPER_OPTIMIZER_DEBUG
            else {
              std::cout << "    same hash, but not equiv to "
                        << SiblingBlock->Id << '\n';
            }
#endif
          }
          if (!Merged) {
            HashedSiblings.emplace_back(CurrBranch, CurrBlock);
          }
        }
        for (auto* Curr : BlocksToErase) {
          ParentBlock->BranchesOut.erase(Curr);
        }
      }
    }
    return Worked;
  }

  // Merge consecutive blocks, that is, A -> B where no other branches go to B.
  // In that case we are guaranteed to not increase code size.
  bool MergeConsecutiveBlocks() {
    bool Worked = false;
    // First, count predecessors.
    std::map<Block*, size_t> NumPredecessors;
    for (auto& CurrBlock : Parent->Blocks) {
      for (auto& iter : CurrBlock->BranchesOut) {
        auto* NextBlock = iter.first;
        NumPredecessors[NextBlock]++;
      }
    }
    NumPredecessors[Entry]++;
    for (auto& CurrBlock : Parent->Blocks) {
      if (CurrBlock->BranchesOut.size() == 1) {
        auto iter = CurrBlock->BranchesOut.begin();
        auto* NextBlock = iter->first;
        auto* NextBranch = iter->second;
        assert(NumPredecessors[NextBlock] > 0);
        if (NextBlock != CurrBlock.get() && NumPredecessors[NextBlock] == 1) {
          // Good to merge!
          wasm::Builder Builder(*Parent->Module);
          // Merge in code on the branch as well, if any.
          if (NextBranch->Code) {
            CurrBlock->Code =
              Builder.makeSequence(CurrBlock->Code, NextBranch->Code);
          }
          CurrBlock->Code =
            Builder.makeSequence(CurrBlock->Code, NextBlock->Code);
          // Use the next block's branching behavior
          CurrBlock->BranchesOut.swap(NextBlock->BranchesOut);
          NextBlock->BranchesOut.clear();
          CurrBlock->SwitchCondition = NextBlock->SwitchCondition;
          // The next block now has no predecessors.
          NumPredecessors[NextBlock] = 0;
          Worked = true;
        }
      }
    }
    return Worked;
  }

  // Removes unneeded switches - if only one branch is left, the default, then
  // no switch is needed.
  bool UnSwitch() {
    bool Worked = false;
    for (auto& ParentBlock : Parent->Blocks) {
#if RELOOPER_OPTIMIZER_DEBUG
      std::cout << "un-switching at " << ParentBlock->Id << ' '
                << !!ParentBlock->SwitchCondition << ' '
                << ParentBlock->BranchesOut.size() << '\n';
#endif
      if (ParentBlock->SwitchCondition) {
        if (ParentBlock->BranchesOut.size() <= 1) {
#if RELOOPER_OPTIMIZER_DEBUG
          std::cout << "  un-switching!: " << ParentBlock->Id << '\n';
#endif
          ParentBlock->SwitchCondition = nullptr;
          if (!ParentBlock->BranchesOut.empty()) {
            assert(!ParentBlock->BranchesOut.begin()->second->SwitchValues);
          }
          Worked = true;
        }
      } else {
        // If the block has no switch, the branches must not as well.
#ifndef NDEBUG
        for (auto& [_, CurrBranch] : ParentBlock->BranchesOut) {
          assert(!CurrBranch->SwitchValues);
        }
#endif
      }
    }
    return Worked;
  }

private:
  wasm::Expression* Canonicalize(wasm::Expression* Curr) {
    wasm::Builder Builder(*Parent->Module);
    // Our preferred form is a block with no name and a flat list
    // with Nops removed, and extra Unreachables removed as well.
    // If the block would contain one item, return just the item.
    wasm::Block* Outer = Curr->dynCast<wasm::Block>();
    if (!Outer) {
      Outer = Builder.makeBlock(Curr);
    } else if (Outer->name.is()) {
      // Perhaps the name can be removed.
      if (!wasm::BranchUtils::BranchSeeker::has(Outer, Outer->name)) {
        Outer->name = wasm::Name();
      } else {
        Outer = Builder.makeBlock(Curr);
      }
    }
    Flatten(Outer);
    if (Outer->list.size() == 1) {
      return Outer->list[0];
    } else {
      return Outer;
    }
  }

  void Flatten(wasm::Block* Outer) {
    wasm::ExpressionList NewList(Parent->Module->allocator);
    bool SeenUnreachableType = false;
    auto Add = [&](wasm::Expression* Curr) {
      if (Curr->is<wasm::Nop>()) {
        // Do nothing with it.
        return;
      } else if (Curr->is<wasm::Unreachable>()) {
        // If we already saw an unreachable-typed item, emit no
        // Unreachable nodes after it.
        if (SeenUnreachableType) {
          return;
        }
      }
      NewList.push_back(Curr);
      if (Curr->type == wasm::Type::unreachable) {
        SeenUnreachableType = true;
      }
    };
    std::function<void(wasm::Block*)> FlattenIntoNewList =
      [&](wasm::Block* Curr) {
        assert(!Curr->name.is());
        for (auto* Item : Curr->list) {
          if (auto* Block = Item->dynCast<wasm::Block>()) {
            if (Block->name.is()) {
              // Leave it whole, it's not a trivial block.
              Add(Block);
            } else {
              FlattenIntoNewList(Block);
            }
          } else {
            // A random item.
            Add(Item);
          }
        }
        // All the items have been moved out.
        Curr->list.clear();
      };
    FlattenIntoNewList(Outer);
    assert(Outer->list.empty());
    Outer->list.swap(NewList);
  }

  bool IsEmpty(Block* Curr) {
    if (Curr->SwitchCondition) {
      // This is non-trivial, so treat it as a non-empty block.
      return false;
    }
    return IsEmpty(Curr->Code);
  }

  bool IsEmpty(wasm::Expression* Code) {
    if (Code->is<wasm::Nop>()) {
      return true; // a nop
    }
    if (auto* WasmBlock = Code->dynCast<wasm::Block>()) {
      for (auto* Item : WasmBlock->list) {
        if (!IsEmpty(Item)) {
          return false;
        }
      }
      return true; // block with no non-empty contents
    }
    return false;
  }

  // Checks functional equivalence, namely: the Code and SwitchCondition.
  // We also check the branches out, *non-recursively*: that is, we check
  // that they are literally identical, not that they can be computed to
  // be equivalent.
  bool HaveEquivalentContents(Block* A, Block* B) {
    if (!IsPossibleCodeEquivalent(A->SwitchCondition, B->SwitchCondition)) {
      return false;
    }
    if (!IsCodeEquivalent(A->Code, B->Code)) {
      return false;
    }
    if (A->BranchesOut.size() != B->BranchesOut.size()) {
      return false;
    }
    for (auto& [ABlock, ABranch] : A->BranchesOut) {
      if (B->BranchesOut.count(ABlock) == 0) {
        return false;
      }
      auto* BBranch = B->BranchesOut[ABlock];
      if (!IsPossibleCodeEquivalent(ABranch->Condition, BBranch->Condition)) {
        return false;
      }
      if (!IsPossibleUniquePtrEquivalent(ABranch->SwitchValues,
                                         BBranch->SwitchValues)) {
        return false;
      }
      if (!IsPossibleCodeEquivalent(ABranch->Code, BBranch->Code)) {
        return false;
      }
    }
    return true;
  }

  // Checks if values referred to by pointers are identical, allowing the code
  // to also be nullptr
  template<typename T>
  static bool IsPossibleUniquePtrEquivalent(std::unique_ptr<T>& A,
                                            std::unique_ptr<T>& B) {
    if (A == B) {
      return true;
    }
    if (!A || !B) {
      return false;
    }
    return *A == *B;
  }

  // Checks if code is equivalent, allowing the code to also be nullptr
  static bool IsPossibleCodeEquivalent(wasm::Expression* A,
                                       wasm::Expression* B) {
    if (A == B) {
      return true;
    }
    if (!A || !B) {
      return false;
    }
    return IsCodeEquivalent(A, B);
  }

  static bool IsCodeEquivalent(wasm::Expression* A, wasm::Expression* B) {
    return wasm::ExpressionAnalyzer::equal(A, B);
  }

  // Merges one branch into another. Valid under the assumption that the
  // blocks they reach are identical, and so one branch is enough for both
  // with a unified condition.
  // Only one is allowed to have code, as the code may have side effects,
  // and we don't have a way to order or resolve those, unless the code
  // is equivalent.
  void MergeBranchInto(Branch* Curr, Branch* Into) {
    assert(Curr != Into);
    if (Curr->SwitchValues) {
      if (!Into->SwitchValues) {
        assert(!Into->Condition);
        // Merging into the already-default, nothing to do.
      } else {
        Into->SwitchValues->insert(Into->SwitchValues->end(),
                                   Curr->SwitchValues->begin(),
                                   Curr->SwitchValues->end());
      }
    } else {
      if (!Curr->Condition) {
        // This is now the new default. Whether Into has a condition
        // or switch values, remove them all to make us the default.
        Into->Condition = nullptr;
        Into->SwitchValues.reset();
      } else if (!Into->Condition) {
        // Nothing to do, already the default.
      } else {
        assert(!Into->SwitchValues);
        // Merge them, checking both.
        Into->Condition =
          wasm::Builder(*Parent->Module)
            .makeBinary(wasm::OrInt32, Into->Condition, Curr->Condition);
      }
    }
    if (!Curr->Code) {
      // No code to merge in.
    } else if (!Into->Code) {
      // Just use the code being merged in.
      Into->Code = Curr->Code;
    } else {
      assert(IsCodeEquivalent(Into->Code, Curr->Code));
      // Keep the code already there, either is fine.
    }
  }

  // Hashes the direct block contents, but not Relooper internals
  // (like Shapes). Only partially hashes the branches out, no
  // recursion: hashes the branch infos, looks at raw pointers
  // for the blocks.
  size_t Hash(Block* Curr) {
    auto digest = wasm::ExpressionAnalyzer::hash(Curr->Code);
    wasm::rehash(digest, uint8_t(1));
    if (Curr->SwitchCondition) {
      wasm::hash_combine(digest,
                         wasm::ExpressionAnalyzer::hash(Curr->SwitchCondition));
    }
    wasm::rehash(digest, uint8_t(2));
    for (auto& [CurrBlock, CurrBranch] : Curr->BranchesOut) {
      // Hash the Block* as a pointer TODO: full hash?
      wasm::rehash(digest, reinterpret_cast<size_t>(CurrBlock));
      // Hash the Branch info properly
      wasm::hash_combine(digest, Hash(CurrBranch));
    }
    return digest;
  }

  // Hashes the direct block contents, but not Relooper internals
  // (like Shapes).
  size_t Hash(Branch* Curr) {
    auto digest = wasm::hash(0);
    if (Curr->SwitchValues) {
      for (auto i : *Curr->SwitchValues) {
        wasm::rehash(digest, i); // TODO hash i
      }
    } else {
      if (Curr->Condition) {
        wasm::hash_combine(digest,
                           wasm::ExpressionAnalyzer::hash(Curr->Condition));
      }
    }
    wasm::rehash(digest, uint8_t(1));
    if (Curr->Code) {
      wasm::hash_combine(digest, wasm::ExpressionAnalyzer::hash(Curr->Code));
    }
    return digest;
  }
};

} // namespace

void Relooper::Calculate(Block* Entry) {
  // Optimize.
  Optimizer(this, Entry);

  // Find live blocks.
  Liveness Live(this);
  Live.FindLive(Entry);

  // Add incoming branches from live blocks, ignoring dead code
  for (unsigned i = 0; i < Blocks.size(); i++) {
    Block* Curr = Blocks[i].get();
    if (!contains(Live.Live, Curr)) {
      continue;
    }
    for (auto& [CurrBlock, _] : Curr->BranchesOut) {
      CurrBlock->BranchesIn.insert(Curr);
    }
  }

  // Recursively process the graph

  struct Analyzer : public RelooperRecursor {
    Analyzer(Relooper* Parent) : RelooperRecursor(Parent) {}

    // Create a list of entries from a block. If LimitTo is provided, only
    // results in that set will appear
    void GetBlocksOut(Block* Source,
                      BlockSet& Entries,
                      BlockSet* LimitTo = nullptr) {
      for (auto& [CurrBlock, _] : Source->BranchesOut) {
        if (!LimitTo || contains(*LimitTo, CurrBlock)) {
          Entries.insert(CurrBlock);
        }
      }
    }

    // Converts/processes all branchings to a specific target
    void Solipsize(Block* Target,
                   Branch::FlowType Type,
                   Shape* Ancestor,
                   BlockSet& From) {
      PrintDebug("Solipsizing branches into %d\n", Target->Id);
      DebugDump(From, "  relevant to solipsize: ");
      for (auto iter = Target->BranchesIn.begin();
           iter != Target->BranchesIn.end();) {
        Block* Prior = *iter;
        if (!contains(From, Prior)) {
          iter++;
          continue;
        }
        Branch* PriorOut = Prior->BranchesOut[Target];
        PriorOut->Ancestor = Ancestor;
        PriorOut->Type = Type;
        iter++; // carefully increment iter before erasing
        Target->BranchesIn.erase(Prior);
        Target->ProcessedBranchesIn.insert(Prior);
        Prior->BranchesOut.erase(Target);
        Prior->ProcessedBranchesOut[Target] = PriorOut;
        PrintDebug("  eliminated branch from %d\n", Prior->Id);
      }
    }

    Shape* MakeSimple(BlockSet& Blocks, Block* Inner, BlockSet& NextEntries) {
      PrintDebug("creating simple block with block #%d\n", Inner->Id);
      SimpleShape* Simple = Parent->AddSimpleShape();
      Simple->Inner = Inner;
      Inner->Parent = Simple;
      if (Blocks.size() > 1) {
        Blocks.erase(Inner);
        GetBlocksOut(Inner, NextEntries, &Blocks);
        BlockSet JustInner;
        JustInner.insert(Inner);
        for (auto* Next : NextEntries) {
          Solipsize(Next, Branch::Break, Simple, JustInner);
        }
      }
      return Simple;
    }

    Shape*
    MakeLoop(BlockSet& Blocks, BlockSet& Entries, BlockSet& NextEntries) {
      // Find the inner blocks in this loop. Proceed backwards from the entries
      // until you reach a seen block, collecting as you go.
      BlockSet InnerBlocks;
      BlockSet Queue = Entries;
      while (Queue.size() > 0) {
        Block* Curr = *(Queue.begin());
        Queue.erase(Queue.begin());
        if (!contains(InnerBlocks, Curr)) {
          // This element is new, mark it as inner and remove from outer
          InnerBlocks.insert(Curr);
          Blocks.erase(Curr);
          // Add the elements prior to it
          for (auto* Prev : Curr->BranchesIn) {
            Queue.insert(Prev);
          }
#if 0
          // Add elements it leads to, if they are dead ends. There is no reason not to hoist dead ends
          // into loops, as it can avoid multiple entries after the loop
          for (auto iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) {
            Block* Target = iter->first;
            if (Target->BranchesIn.size() <= 1 && Target->BranchesOut.size() == 0) {
              Queue.insert(Target);
            }
          }
#endif
        }
      }
      assert(InnerBlocks.size() > 0);

      for (auto* Curr : InnerBlocks) {
        for (auto& [Possible, _] : Curr->BranchesOut) {
          if (!contains(InnerBlocks, Possible)) {
            NextEntries.insert(Possible);
          }
        }
      }

#if 0
      // We can avoid multiple next entries by hoisting them into the loop.
      if (NextEntries.size() > 1) {
        BlockBlockSetMap IndependentGroups;
        FindIndependentGroups(NextEntries, IndependentGroups, &InnerBlocks);

        while (IndependentGroups.size() > 0 && NextEntries.size() > 1) {
          Block* Min = nullptr;
          int MinSize = 0;
          for (auto iter = IndependentGroups.begin(); iter != IndependentGroups.end(); iter++) {
            Block* Entry = iter->first;
            BlockSet &Blocks = iter->second;
            if (!Min || Blocks.size() < MinSize) { // TODO: code size, not # of blocks
              Min = Entry;
              MinSize = Blocks.size();
            }
          }
          // check how many new entries this would cause
          BlockSet &Hoisted = IndependentGroups[Min];
          bool abort = false;
          for (auto iter = Hoisted.begin(); iter != Hoisted.end() && !abort; iter++) {
            Block* Curr = *iter;
            for (auto iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) {
              Block* Target = iter->first;
              if (!contains(Hoisted, Target) && !contains(NextEntries, Target)) {
                // abort this hoisting
                abort = true;
                break;
              }
            }
          }
          if (abort) {
            IndependentGroups.erase(Min);
            continue;
          }
          // hoist this entry
          PrintDebug("hoisting %d into loop\n", Min->Id);
          NextEntries.erase(Min);
          for (auto iter = Hoisted.begin(); iter != Hoisted.end(); iter++) {
            Block* Curr = *iter;
            InnerBlocks.insert(Curr);
            Blocks.erase(Curr);
          }
          IndependentGroups.erase(Min);
        }
      }
#endif

      PrintDebug("creating loop block:\n", 0);
      DebugDump(InnerBlocks, "  inner blocks:");
      DebugDump(Entries, "  inner entries:");
      DebugDump(Blocks, "  outer blocks:");
      DebugDump(NextEntries, "  outer entries:");

      LoopShape* Loop = Parent->AddLoopShape();

      // Solipsize the loop, replacing with break/continue and marking branches
      // as Processed (will not affect later calculations) A. Branches to the
      // loop entries become a continue to this shape
      for (auto* Entry : Entries) {
        Solipsize(Entry, Branch::Continue, Loop, InnerBlocks);
      }
      // B. Branches to outside the loop (a next entry) become breaks on this
      // shape
      for (auto* Next : NextEntries) {
        Solipsize(Next, Branch::Break, Loop, InnerBlocks);
      }
      // Finish up
      Shape* Inner = Process(InnerBlocks, Entries);
      Loop->Inner = Inner;
      Loop->Entries = Entries;
      return Loop;
    }

    // For each entry, find the independent group reachable by it. The
    // independent group is the entry itself, plus all the blocks it can reach
    // that cannot be directly reached by another entry. Note that we ignore
    // directly reaching the entry itself by another entry.
    //   @param Ignore - previous blocks that are irrelevant
    void FindIndependentGroups(BlockSet& Entries,
                               BlockBlockSetMap& IndependentGroups,
                               BlockSet* Ignore = nullptr) {
      using BlockBlockMap = std::map<Block*, Block*>;

      struct HelperClass {
        BlockBlockSetMap& IndependentGroups;
        // For each block, which entry it belongs to. We have reached it from
        // there.
        BlockBlockMap Ownership;

        HelperClass(BlockBlockSetMap& IndependentGroupsInit)
          : IndependentGroups(IndependentGroupsInit) {}
        void InvalidateWithChildren(Block* New) { // TODO: rename New
          // Being in the list means you need to be invalidated
          BlockList ToInvalidate;
          ToInvalidate.push_back(New);
          while (ToInvalidate.size() > 0) {
            Block* Invalidatee = ToInvalidate.front();
            ToInvalidate.pop_front();
            Block* Owner = Ownership[Invalidatee];
            // Owner may have been invalidated, do not add to IndependentGroups!
            if (contains(IndependentGroups, Owner)) {
              IndependentGroups[Owner].erase(Invalidatee);
            }
            // may have been seen before and invalidated already
            if (Ownership[Invalidatee]) {
              Ownership[Invalidatee] = nullptr;
              for (auto& [Target, _] : Invalidatee->BranchesOut) {
                auto Known = Ownership.find(Target);
                if (Known != Ownership.end()) {
                  Block* TargetOwner = Known->second;
                  if (TargetOwner) {
                    ToInvalidate.push_back(Target);
                  }
                }
              }
            }
          }
        }
      };
      HelperClass Helper(IndependentGroups);

      // We flow out from each of the entries, simultaneously.
      // When we reach a new block, we add it as belonging to the one we got to
      // it from. If we reach a new block that is already marked as belonging to
      // someone, it is reachable by two entries and is not valid for any of
      // them. Remove it and all it can reach that have been visited.

      // Being in the queue means we just added this item, and we need to add
      // its children
      BlockList Queue;
      for (auto* Entry : Entries) {
        Helper.Ownership[Entry] = Entry;
        IndependentGroups[Entry].insert(Entry);
        Queue.push_back(Entry);
      }
      while (Queue.size() > 0) {
        Block* Curr = Queue.front();
        Queue.pop_front();
        // Curr must be in the ownership map if we are in the queue
        Block* Owner = Helper.Ownership[Curr];
        if (!Owner) {
          // we have been invalidated meanwhile after being reached from two
          // entries
          continue;
        }
        // Add all children
        for (auto& [New, _] : Curr->BranchesOut) {
          auto Known = Helper.Ownership.find(New);
          if (Known == Helper.Ownership.end()) {
            // New node. Add it, and put it in the queue
            Helper.Ownership[New] = Owner;
            IndependentGroups[Owner].insert(New);
            Queue.push_back(New);
            continue;
          }
          Block* NewOwner = Known->second;
          if (!NewOwner) {
            continue; // We reached an invalidated node
          }
          if (NewOwner != Owner) {
            // Invalidate this and all reachable that we have seen - we reached
            // this from two locations
            Helper.InvalidateWithChildren(New);
          }
          // otherwise, we have the same owner, so do nothing
        }
      }

      // Having processed all the interesting blocks, we remain with just one
      // potential issue: If a->b, and a was invalidated, but then b was later
      // reached by someone else, we must invalidate b. To check for this, we go
      // over all elements in the independent groups, if an element has a parent
      // which does *not* have the same owner, we must remove it and all its
      // children.

      for (auto* Entry : Entries) {
        BlockSet& CurrGroup = IndependentGroups[Entry];
        BlockList ToInvalidate;
        for (auto* Child : CurrGroup) {
          for (auto* Parent : Child->BranchesIn) {
            if (Ignore && contains(*Ignore, Parent)) {
              continue;
            }
            if (Helper.Ownership[Parent] != Helper.Ownership[Child]) {
              ToInvalidate.push_back(Child);
            }
          }
        }
        while (ToInvalidate.size() > 0) {
          Block* Invalidatee = ToInvalidate.front();
          ToInvalidate.pop_front();
          Helper.InvalidateWithChildren(Invalidatee);
        }
      }

      // Remove empty groups
      for (auto* Entry : Entries) {
        if (IndependentGroups[Entry].size() == 0) {
          IndependentGroups.erase(Entry);
        }
      }

#ifdef RELOOPER_DEBUG
      PrintDebug("Investigated independent groups:\n");
      for (auto& iter : IndependentGroups) {
        DebugDump(iter.second, " group: ");
      }
#endif
    }

    Shape* MakeMultiple(BlockSet& Blocks,
                        BlockSet& Entries,
                        BlockBlockSetMap& IndependentGroups,
                        BlockSet& NextEntries,
                        bool IsCheckedMultiple) {
      PrintDebug("creating multiple block with %d inner groups\n",
                 IndependentGroups.size());
      MultipleShape* Multiple = Parent->AddMultipleShape();
      BlockSet CurrEntries;
      for (auto& [CurrEntry, CurrBlocks] : IndependentGroups) {
        PrintDebug("  multiple group with entry %d:\n", CurrEntry->Id);
        DebugDump(CurrBlocks, "    ");
        // Create inner block
        CurrEntries.clear();
        CurrEntries.insert(CurrEntry);
        for (auto* CurrInner : CurrBlocks) {
          // Remove the block from the remaining blocks
          Blocks.erase(CurrInner);
          // Find new next entries and fix branches to them
          for (auto iter = CurrInner->BranchesOut.begin();
               iter != CurrInner->BranchesOut.end();) {
            Block* CurrTarget = iter->first;
            auto Next = iter;
            Next++;
            if (!contains(CurrBlocks, CurrTarget)) {
              NextEntries.insert(CurrTarget);
              Solipsize(CurrTarget, Branch::Break, Multiple, CurrBlocks);
            }
            iter = Next; // increment carefully because Solipsize can remove us
          }
        }
        Multiple->InnerMap[CurrEntry->Id] = Process(CurrBlocks, CurrEntries);
        if (IsCheckedMultiple) {
          CurrEntry->IsCheckedMultipleEntry = true;
        }
      }
      DebugDump(Blocks, "  remaining blocks after multiple:");
      // Add entries not handled as next entries, they are deferred
      for (auto* Entry : Entries) {
        if (!contains(IndependentGroups, Entry)) {
          NextEntries.insert(Entry);
        }
      }
      return Multiple;
    }

    // Main function.
    // Process a set of blocks with specified entries, returns a shape
    // The Make* functions receive a NextEntries. If they fill it with data,
    // those are the entries for the
    //   ->Next block on them, and the blocks are what remains in Blocks (which
    //   Make* modify). In this way we avoid recursing on Next (imagine a long
    //   chain of Simples, if we recursed we could blow the stack).
    Shape* Process(BlockSet& Blocks, BlockSet& InitialEntries) {
      PrintDebug("Process() called\n", 0);
      BlockSet* Entries = &InitialEntries;
      BlockSet TempEntries[2];
      int CurrTempIndex = 0;
      BlockSet* NextEntries;
      Shape* Ret = nullptr;
      Shape* Prev = nullptr;
#define Make(call)                                                             \
  Shape* Temp = call;                                                          \
  if (Prev)                                                                    \
    Prev->Next = Temp;                                                         \
  if (!Ret)                                                                    \
    Ret = Temp;                                                                \
  if (!NextEntries->size()) {                                                  \
    PrintDebug("Process() returning\n", 0);                                    \
    return Ret;                                                                \
  }                                                                            \
  Prev = Temp;                                                                 \
  Entries = NextEntries;                                                       \
  continue;
      while (1) {
        PrintDebug("Process() running\n", 0);
        DebugDump(Blocks, "  blocks : ");
        DebugDump(*Entries, "  entries: ");

        CurrTempIndex = 1 - CurrTempIndex;
        NextEntries = &TempEntries[CurrTempIndex];
        NextEntries->clear();

        if (Entries->size() == 0) {
          return Ret;
        }
        if (Entries->size() == 1) {
          Block* Curr = *(Entries->begin());
          if (Curr->BranchesIn.size() == 0) {
            // One entry, no looping ==> Simple
            Make(MakeSimple(Blocks, Curr, *NextEntries));
          }
          // One entry, looping ==> Loop
          Make(MakeLoop(Blocks, *Entries, *NextEntries));
        }

        // More than one entry, try to eliminate through a Multiple groups of
        // independent blocks from an entry/ies. It is important to remove
        // through multiples as opposed to looping since the former is more
        // performant.
        BlockBlockSetMap IndependentGroups;
        FindIndependentGroups(*Entries, IndependentGroups);

        PrintDebug("Independent groups: %d\n", IndependentGroups.size());

        if (IndependentGroups.size() > 0) {
          // We can handle a group in a multiple if its entry cannot be reached
          // by another group. Note that it might be reachable by itself - a
          // loop. But that is fine, we will create a loop inside the multiple
          // block, which is both the performant order to do it, and preserves
          // the property that a loop will always reach an entry.
          for (auto iter = IndependentGroups.begin();
               iter != IndependentGroups.end();) {
            Block* Entry = iter->first;
            BlockSet& Group = iter->second;
            auto curr = iter++; // iterate carefully, we may delete
            for (auto iterBranch = Entry->BranchesIn.begin();
                 iterBranch != Entry->BranchesIn.end();
                 iterBranch++) {
              Block* Origin = *iterBranch;
              if (!contains(Group, Origin)) {
                // Reached from outside the group, so we cannot handle this
                PrintDebug("Cannot handle group with entry %d because of "
                           "incoming branch from %d\n",
                           Entry->Id,
                           Origin->Id);
                IndependentGroups.erase(curr);
                break;
              }
            }
          }

          // As an optimization, if we have 2 independent groups, and one is a
          // small dead end, we can handle only that dead end. The other then
          // becomes a Next - without nesting in the code and recursion in the
          // analysis.
          // TODO: if the larger is the only dead end, handle that too
          // TODO: handle >2 groups
          // TODO: handle not just dead ends, but also that do not branch to the
          //       NextEntries. However, must be careful
          //       there since we create a Next, and that Next can prevent
          //       eliminating a break (since we no longer naturally reach the
          //       same place), which may necessitate a one-time loop, which
          //       makes the unnesting pointless.
          if (IndependentGroups.size() == 2) {
            // Find the smaller one
            auto iter = IndependentGroups.begin();
            Block* SmallEntry = iter->first;
            int SmallSize = iter->second.size();
            iter++;
            Block* LargeEntry = iter->first;
            int LargeSize = iter->second.size();
            // ignore the case where they are identical - keep things
            // symmetrical there
            if (SmallSize != LargeSize) {
              if (SmallSize > LargeSize) {
                Block* Temp = SmallEntry;
                SmallEntry = LargeEntry;
                // Note: we did not flip the Sizes too, they are now invalid.
                // TODO: use the smaller size as a limit?
                LargeEntry = Temp;
              }
              // Check if dead end
              bool DeadEnd = true;
              BlockSet& SmallGroup = IndependentGroups[SmallEntry];
              for (auto* Curr : SmallGroup) {
                for (auto& [Target, _] : Curr->BranchesOut) {
                  if (!contains(SmallGroup, Target)) {
                    DeadEnd = false;
                    break;
                  }
                }
                if (!DeadEnd) {
                  break;
                }
              }
              if (DeadEnd) {
                PrintDebug("Removing nesting by not handling large group "
                           "because small group is dead end\n",
                           0);
                IndependentGroups.erase(LargeEntry);
              }
            }
          }

          PrintDebug("Handleable independent groups: %d\n",
                     IndependentGroups.size());

          if (IndependentGroups.size() > 0) {
            // Some groups removable ==> Multiple
            // This is a checked multiple if it has an entry that is an entry to
            // this Process call, that is, if we can reach it from outside this
            // set of blocks, then we must check the label variable to do so.
            // Otherwise, if it is just internal blocks, those can always be
            // jumped to forward, without using the label variable
            bool Checked = false;
            for (auto* Entry : *Entries) {
              if (InitialEntries.count(Entry)) {
                Checked = true;
                break;
              }
            }
            Make(MakeMultiple(
              Blocks, *Entries, IndependentGroups, *NextEntries, Checked));
          }
        }
        // No independent groups, must be loopable ==> Loop
        Make(MakeLoop(Blocks, *Entries, *NextEntries));
      }
    }
  };

  // Main

  BlockSet AllBlocks;
  for (auto* Curr : Live.Live) {
    AllBlocks.insert(Curr);
#ifdef RELOOPER_DEBUG
    PrintDebug("Adding block %d (%s)\n", Curr->Id, Curr->Code);
#endif
  }

  BlockSet Entries;
  Entries.insert(Entry);
  Root = Analyzer(this).Process(AllBlocks, Entries);
  assert(Root);
}

wasm::Expression* Relooper::Render(RelooperBuilder& Builder) {
  assert(Root);
  auto* ret = Root->Render(Builder, false);
  // we may use the same name for more than one block in HandleFollowupMultiples
  wasm::UniqueNameMapper::uniquify(ret);
  return ret;
}

#ifdef RELOOPER_DEBUG
// Debugging

void Debugging::Dump(Block* Curr, const char* prefix) {
  if (prefix)
    std::cout << prefix << ": ";
  std::cout << Curr->Id << " [code " << *Curr->Code << "] [switch? "
            << !!Curr->SwitchCondition << "]\n";
  for (auto iter2 = Curr->BranchesOut.begin(); iter2 != Curr->BranchesOut.end();
       iter2++) {
    Block* Other = iter2->first;
    Branch* Br = iter2->second;
    std::cout << "  -> " << Other->Id << ' ';
    if (Br->Condition) {
      std::cout << "[if " << *Br->Condition << "] ";
    } else if (Br->SwitchValues) {
      std::cout << "[cases ";
      for (auto x : *Br->SwitchValues) {
        std::cout << x << ' ';
      }
      std::cout << "] ";
    } else {
      std::cout << "[default] ";
    }
    if (Br->Code)
      std::cout << "[phi " << *Br->Code << "] ";
    std::cout << '\n';
  }
  std::cout << '\n';
}

void Debugging::Dump(BlockSet& Blocks, const char* prefix) {
  if (prefix)
    std::cout << prefix << ": ";
  for (auto* Curr : Blocks) {
    Dump(Curr);
  }
}

void Debugging::Dump(Shape* S, const char* prefix) {
  if (prefix)
    std::cout << prefix << ": ";
  if (!S) {
    printf(" (null)\n");
    return;
  }
  printf(" %d ", S->Id);
  if (SimpleShape* Simple = Shape::IsSimple(S)) {
    printf("<< Simple with block %d\n", Simple->Inner->Id);
  } else if (MultipleShape* Multiple = Shape::IsMultiple(S)) {
    printf("<< Multiple\n");
    for (auto& [Entry, _] : Multiple->InnerMap) {
      printf("     with entry %d\n", Entry);
    }
  } else if (Shape::IsLoop(S)) {
    printf("<< Loop\n");
  } else {
    abort();
  }
}

static void PrintDebug(const char* Format, ...) {
  printf("// ");
  va_list Args;
  va_start(Args, Format);
  vprintf(Format, Args);
  va_end(Args);
}
#endif

} // namespace CFG
