/*
 * Copyright (C) 2019 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 "load_store_elimination.h"

#include <initializer_list>
#include <memory>
#include <tuple>
#include <variant>

#include "base/iteration_range.h"
#include "compilation_kind.h"
#include "dex/dex_file_types.h"
#include "entrypoints/quick/quick_entrypoints.h"
#include "entrypoints/quick/quick_entrypoints_enum.h"
#include "gtest/gtest.h"
#include "handle_scope.h"
#include "load_store_analysis.h"
#include "nodes.h"
#include "optimizing/data_type.h"
#include "optimizing/instruction_simplifier.h"
#include "optimizing/optimizing_compiler_stats.h"
#include "optimizing_unit_test.h"
#include "scoped_thread_state_change.h"

namespace art {

#define CHECK_SUBROUTINE_FAILURE() \
  do {                             \
    if (HasFatalFailure()) {       \
      return;                      \
    }                              \
  } while (false)

template <typename SuperTest>
class LoadStoreEliminationTestBase : public SuperTest, public OptimizingUnitTestHelper {
 public:
  void SetUp() override {
    SuperTest::SetUp();
    gLogVerbosity.compiler = true;
  }

  void TearDown() override {
    SuperTest::TearDown();
    gLogVerbosity.compiler = false;
  }

  void PerformLSE(bool with_partial = true) {
    graph_->BuildDominatorTree();
    LoadStoreElimination lse(graph_, /*stats=*/nullptr);
    lse.Run(with_partial);
    std::ostringstream oss;
    EXPECT_TRUE(CheckGraphSkipRefTypeInfoChecks(oss)) << oss.str();
  }

  void PerformLSEWithPartial() {
    PerformLSE(true);
  }

  void PerformLSENoPartial() {
    PerformLSE(false);
  }

  // Create instructions shared among tests.
  void CreateEntryBlockInstructions() {
    HInstruction* c1 = graph_->GetIntConstant(1);
    HInstruction* c4 = graph_->GetIntConstant(4);
    i_add1_ = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_, c1);
    i_add4_ = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_, c4);
    entry_block_->AddInstruction(i_add1_);
    entry_block_->AddInstruction(i_add4_);
    entry_block_->AddInstruction(new (GetAllocator()) HGoto());
  }

  // Create the major CFG used by tests:
  //    entry
  //      |
  //  pre_header
  //      |
  //    loop[]
  //      |
  //   return
  //      |
  //     exit
  void CreateTestControlFlowGraph() {
    InitGraphAndParameters();
    pre_header_ = AddNewBlock();
    loop_ = AddNewBlock();

    entry_block_->ReplaceSuccessor(return_block_, pre_header_);
    pre_header_->AddSuccessor(loop_);
    loop_->AddSuccessor(loop_);
    loop_->AddSuccessor(return_block_);

    HInstruction* c0 = graph_->GetIntConstant(0);
    HInstruction* c1 = graph_->GetIntConstant(1);
    HInstruction* c128 = graph_->GetIntConstant(128);

    CreateEntryBlockInstructions();

    // pre_header block
    //   phi = 0;
    phi_ = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32);
    loop_->AddPhi(phi_);
    pre_header_->AddInstruction(new (GetAllocator()) HGoto());
    phi_->AddInput(c0);

    // loop block:
    //   suspend_check
    //   phi++;
    //   if (phi >= 128)
    suspend_check_ = new (GetAllocator()) HSuspendCheck();
    HInstruction* inc_phi = new (GetAllocator()) HAdd(DataType::Type::kInt32, phi_, c1);
    HInstruction* cmp = new (GetAllocator()) HGreaterThanOrEqual(phi_, c128);
    HInstruction* hif = new (GetAllocator()) HIf(cmp);
    loop_->AddInstruction(suspend_check_);
    loop_->AddInstruction(inc_phi);
    loop_->AddInstruction(cmp);
    loop_->AddInstruction(hif);
    phi_->AddInput(inc_phi);

    CreateEnvForSuspendCheck();
  }

  void CreateEnvForSuspendCheck() {
    ManuallyBuildEnvFor(suspend_check_, {array_, i_, j_});
  }

  // Create the diamond-shaped CFG:
  //      upper
  //      /   \
  //    left  right
  //      \   /
  //      down
  //
  // Return: the basic blocks forming the CFG in the following order {upper, left, right, down}.
  std::tuple<HBasicBlock*, HBasicBlock*, HBasicBlock*, HBasicBlock*> CreateDiamondShapedCFG() {
    InitGraphAndParameters();
    CreateEntryBlockInstructions();

    HBasicBlock* upper = AddNewBlock();
    HBasicBlock* left = AddNewBlock();
    HBasicBlock* right = AddNewBlock();

    entry_block_->ReplaceSuccessor(return_block_, upper);
    upper->AddSuccessor(left);
    upper->AddSuccessor(right);
    left->AddSuccessor(return_block_);
    right->AddSuccessor(return_block_);

    HInstruction* cmp = new (GetAllocator()) HGreaterThanOrEqual(i_, j_);
    HInstruction* hif = new (GetAllocator()) HIf(cmp);
    upper->AddInstruction(cmp);
    upper->AddInstruction(hif);

    left->AddInstruction(new (GetAllocator()) HGoto());
    right->AddInstruction(new (GetAllocator()) HGoto());

    return std::make_tuple(upper, left, right, return_block_);
  }

  // Add a HVecLoad instruction to the end of the provided basic block.
  //
  // Return: the created HVecLoad instruction.
  HInstruction* AddVecLoad(HBasicBlock* block, HInstruction* array, HInstruction* index) {
    DCHECK(block != nullptr);
    DCHECK(array != nullptr);
    DCHECK(index != nullptr);
    HInstruction* vload =
        new (GetAllocator()) HVecLoad(GetAllocator(),
                                      array,
                                      index,
                                      DataType::Type::kInt32,
                                      SideEffects::ArrayReadOfType(DataType::Type::kInt32),
                                      4,
                                      /*is_string_char_at*/ false,
                                      kNoDexPc);
    block->InsertInstructionBefore(vload, block->GetLastInstruction());
    return vload;
  }

  // Add a HVecStore instruction to the end of the provided basic block.
  // If no vdata is specified, generate HVecStore: array[index] = [1,1,1,1].
  //
  // Return: the created HVecStore instruction.
  HInstruction* AddVecStore(HBasicBlock* block,
                            HInstruction* array,
                            HInstruction* index,
                            HInstruction* vdata = nullptr) {
    DCHECK(block != nullptr);
    DCHECK(array != nullptr);
    DCHECK(index != nullptr);
    if (vdata == nullptr) {
      HInstruction* c1 = graph_->GetIntConstant(1);
      vdata = new (GetAllocator())
          HVecReplicateScalar(GetAllocator(), c1, DataType::Type::kInt32, 4, kNoDexPc);
      block->InsertInstructionBefore(vdata, block->GetLastInstruction());
    }
    HInstruction* vstore =
        new (GetAllocator()) HVecStore(GetAllocator(),
                                       array,
                                       index,
                                       vdata,
                                       DataType::Type::kInt32,
                                       SideEffects::ArrayWriteOfType(DataType::Type::kInt32),
                                       4,
                                       kNoDexPc);
    block->InsertInstructionBefore(vstore, block->GetLastInstruction());
    return vstore;
  }

  // Add a HArrayGet instruction to the end of the provided basic block.
  //
  // Return: the created HArrayGet instruction.
  HInstruction* AddArrayGet(HBasicBlock* block, HInstruction* array, HInstruction* index) {
    DCHECK(block != nullptr);
    DCHECK(array != nullptr);
    DCHECK(index != nullptr);
    HInstruction* get = new (GetAllocator()) HArrayGet(array, index, DataType::Type::kInt32, 0);
    block->InsertInstructionBefore(get, block->GetLastInstruction());
    return get;
  }

  // Add a HArraySet instruction to the end of the provided basic block.
  // If no data is specified, generate HArraySet: array[index] = 1.
  //
  // Return: the created HArraySet instruction.
  HInstruction* AddArraySet(HBasicBlock* block,
                            HInstruction* array,
                            HInstruction* index,
                            HInstruction* data = nullptr) {
    DCHECK(block != nullptr);
    DCHECK(array != nullptr);
    DCHECK(index != nullptr);
    if (data == nullptr) {
      data = graph_->GetIntConstant(1);
    }
    HInstruction* store =
        new (GetAllocator()) HArraySet(array, index, data, DataType::Type::kInt32, 0);
    block->InsertInstructionBefore(store, block->GetLastInstruction());
    return store;
  }

  void InitGraphAndParameters() {
    InitGraph();
    AddParameter(new (GetAllocator()) HParameterValue(
        graph_->GetDexFile(), dex::TypeIndex(0), 0, DataType::Type::kInt32));
    array_ = parameters_.back();
    AddParameter(new (GetAllocator()) HParameterValue(
        graph_->GetDexFile(), dex::TypeIndex(1), 1, DataType::Type::kInt32));
    i_ = parameters_.back();
    AddParameter(new (GetAllocator()) HParameterValue(
        graph_->GetDexFile(), dex::TypeIndex(1), 2, DataType::Type::kInt32));
    j_ = parameters_.back();
  }

  HBasicBlock* pre_header_;
  HBasicBlock* loop_;

  HInstruction* array_;
  HInstruction* i_;
  HInstruction* j_;
  HInstruction* i_add1_;
  HInstruction* i_add4_;
  HInstruction* suspend_check_;

  HPhi* phi_;
};

class LoadStoreEliminationTest : public LoadStoreEliminationTestBase<CommonCompilerTest> {};

enum class TestOrder { kSameAsAlloc, kReverseOfAlloc };
std::ostream& operator<<(std::ostream& os, const TestOrder& ord) {
  switch (ord) {
    case TestOrder::kSameAsAlloc:
      return os << "SameAsAlloc";
    case TestOrder::kReverseOfAlloc:
      return os << "ReverseOfAlloc";
  }
}

class OrderDependentTestGroup
    : public LoadStoreEliminationTestBase<CommonCompilerTestWithParam<TestOrder>> {};

// Various configs we can use for testing. Currently used in PartialComparison tests.
struct PartialComparisonKind {
 public:
  enum class Type : uint8_t { kEquals, kNotEquals };
  enum class Target : uint8_t { kNull, kValue, kSelf };
  enum class Position : uint8_t { kLeft, kRight };

  const Type type_;
  const Target target_;
  const Position position_;

  bool IsDefinitelyFalse() const {
    return !IsPossiblyTrue();
  }
  bool IsPossiblyFalse() const {
    return !IsDefinitelyTrue();
  }
  bool IsDefinitelyTrue() const {
    if (target_ == Target::kSelf) {
      return type_ == Type::kEquals;
    } else if (target_ == Target::kNull) {
      return type_ == Type::kNotEquals;
    } else {
      return false;
    }
  }
  bool IsPossiblyTrue() const {
    if (target_ == Target::kSelf) {
      return type_ == Type::kEquals;
    } else if (target_ == Target::kNull) {
      return type_ == Type::kNotEquals;
    } else {
      return true;
    }
  }
  std::ostream& Dump(std::ostream& os) const {
    os << "PartialComparisonKind{" << (type_ == Type::kEquals ? "kEquals" : "kNotEquals") << ", "
       << (target_ == Target::kNull ? "kNull" : (target_ == Target::kSelf ? "kSelf" : "kValue"))
       << ", " << (position_ == Position::kLeft ? "kLeft" : "kRight") << "}";
    return os;
  }
};

std::ostream& operator<<(std::ostream& os, const PartialComparisonKind& comp) {
  return comp.Dump(os);
}

class PartialComparisonTestGroup
    : public LoadStoreEliminationTestBase<CommonCompilerTestWithParam<PartialComparisonKind>> {
 public:
  enum class ComparisonPlacement {
    kBeforeEscape,
    kInEscape,
    kAfterEscape,
  };
  void CheckFinalInstruction(HInstruction* ins, ComparisonPlacement placement) {
    using Target = PartialComparisonKind::Target;
    using Type = PartialComparisonKind::Type;
    using Position = PartialComparisonKind::Position;
    PartialComparisonKind kind = GetParam();
    if (ins->IsIntConstant()) {
      if (kind.IsDefinitelyTrue()) {
        EXPECT_TRUE(ins->AsIntConstant()->IsTrue()) << kind << " " << *ins;
      } else if (kind.IsDefinitelyFalse()) {
        EXPECT_TRUE(ins->AsIntConstant()->IsFalse()) << kind << " " << *ins;
      } else {
        EXPECT_EQ(placement, ComparisonPlacement::kBeforeEscape);
        EXPECT_EQ(kind.target_, Target::kValue);
        // We are before escape so value is not the object
        if (kind.type_ == Type::kEquals) {
          EXPECT_TRUE(ins->AsIntConstant()->IsFalse()) << kind << " " << *ins;
        } else {
          EXPECT_TRUE(ins->AsIntConstant()->IsTrue()) << kind << " " << *ins;
        }
      }
      return;
    }
    EXPECT_NE(placement, ComparisonPlacement::kBeforeEscape)
        << "For comparisons before escape we should always be able to transform into a constant."
        << " Instead we got:" << std::endl << ins->DumpWithArgs();
    if (placement == ComparisonPlacement::kInEscape) {
      // Should be the same type.
      ASSERT_TRUE(ins->IsEqual() || ins->IsNotEqual()) << *ins;
      HInstruction* other = kind.position_ == Position::kLeft ? ins->AsBinaryOperation()->GetRight()
                                                              : ins->AsBinaryOperation()->GetLeft();
      if (kind.target_ == Target::kSelf) {
        EXPECT_INS_EQ(ins->AsBinaryOperation()->GetLeft(), ins->AsBinaryOperation()->GetRight())
            << " ins is: " << *ins;
      } else if (kind.target_ == Target::kNull) {
        EXPECT_INS_EQ(other, graph_->GetNullConstant()) << " ins is: " << *ins;
      } else {
        EXPECT_TRUE(other->IsStaticFieldGet()) << " ins is: " << *ins;
      }
      if (kind.type_ == Type::kEquals) {
        EXPECT_TRUE(ins->IsEqual()) << *ins;
      } else {
        EXPECT_TRUE(ins->IsNotEqual()) << *ins;
      }
    } else {
      ASSERT_EQ(placement, ComparisonPlacement::kAfterEscape);
      if (kind.type_ == Type::kEquals) {
        // obj == <anything> can only be true if (1) it's obj == obj or (2) obj has escaped.
        ASSERT_TRUE(ins->IsAnd()) << ins->DumpWithArgs();
        EXPECT_TRUE(ins->InputAt(1)->IsEqual()) << ins->DumpWithArgs();
      } else {
        // obj != <anything> is true if (2) obj has escaped.
        ASSERT_TRUE(ins->IsOr()) << ins->DumpWithArgs();
        EXPECT_TRUE(ins->InputAt(1)->IsNotEqual()) << ins->DumpWithArgs();
      }
      // Check the first part of AND is the obj-has-escaped
      ASSERT_TRUE(ins->InputAt(0)->IsNotEqual()) << ins->DumpWithArgs();
      EXPECT_TRUE(ins->InputAt(0)->InputAt(0)->IsPhi()) << ins->DumpWithArgs();
      EXPECT_TRUE(ins->InputAt(0)->InputAt(1)->IsNullConstant()) << ins->DumpWithArgs();
      // Check the second part of AND is the eq other
      EXPECT_INS_EQ(ins->InputAt(1)->InputAt(kind.position_ == Position::kLeft ? 0 : 1),
                    ins->InputAt(0)->InputAt(0))
          << ins->DumpWithArgs();
    }
  }

  struct ComparisonInstructions {
    void AddSetup(HBasicBlock* blk) const {
      for (HInstruction* i : setup_instructions_) {
        blk->AddInstruction(i);
      }
    }

    void AddEnvironment(HEnvironment* env) const {
      for (HInstruction* i : setup_instructions_) {
        if (i->NeedsEnvironment()) {
          i->CopyEnvironmentFrom(env);
        }
      }
    }

    const std::vector<HInstruction*> setup_instructions_;
    HInstruction* const cmp_;
  };

  ComparisonInstructions GetComparisonInstructions(HInstruction* partial) {
    PartialComparisonKind kind = GetParam();
    std::vector<HInstruction*> setup;
    HInstruction* target_other;
    switch (kind.target_) {
      case PartialComparisonKind::Target::kSelf:
        target_other = partial;
        break;
      case PartialComparisonKind::Target::kNull:
        target_other = graph_->GetNullConstant();
        break;
      case PartialComparisonKind::Target::kValue: {
        HInstruction* cls = MakeClassLoad();
        HInstruction* static_read =
            new (GetAllocator()) HStaticFieldGet(cls,
                                                 /* field= */ nullptr,
                                                 DataType::Type::kReference,
                                                 /* field_offset= */ MemberOffset(40),
                                                 /* is_volatile= */ false,
                                                 /* field_idx= */ 0,
                                                 /* declaring_class_def_index= */ 0,
                                                 graph_->GetDexFile(),
                                                 /* dex_pc= */ 0);
        setup.push_back(cls);
        setup.push_back(static_read);
        target_other = static_read;
        break;
      }
    }
    HInstruction* target_left;
    HInstruction* target_right;
    std::tie(target_left, target_right) = kind.position_ == PartialComparisonKind::Position::kLeft
                                              ? std::pair{partial, target_other}
                                              : std::pair{target_other, partial};
    HInstruction* cmp =
        kind.type_ == PartialComparisonKind::Type::kEquals
            ? static_cast<HInstruction*>(new (GetAllocator()) HEqual(target_left, target_right))
            : static_cast<HInstruction*>(new (GetAllocator()) HNotEqual(target_left, target_right));
    return {setup, cmp};
  }
};

TEST_F(LoadStoreEliminationTest, ArrayGetSetElimination) {
  CreateTestControlFlowGraph();

  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  // array[1] = 1;
  // x = array[1];  <--- Remove.
  // y = array[2];
  // array[1] = 1;  <--- Remove, since it stores same value.
  // array[i] = 3;  <--- MAY alias.
  // array[1] = 1;  <--- Cannot remove, even if it stores the same value.
  AddArraySet(entry_block_, array_, c1, c1);
  HInstruction* load1 = AddArrayGet(entry_block_, array_, c1);
  HInstruction* load2 = AddArrayGet(entry_block_, array_, c2);
  HInstruction* store1 = AddArraySet(entry_block_, array_, c1, c1);
  AddArraySet(entry_block_, array_, i_, c3);
  HInstruction* store2 = AddArraySet(entry_block_, array_, c1, c1);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(load1));
  ASSERT_FALSE(IsRemoved(load2));
  ASSERT_TRUE(IsRemoved(store1));
  ASSERT_FALSE(IsRemoved(store2));
}

TEST_F(LoadStoreEliminationTest, SameHeapValue1) {
  CreateTestControlFlowGraph();

  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  // Test LSE handling same value stores on array.
  // array[1] = 1;
  // array[2] = 1;
  // array[1] = 1;  <--- Can remove.
  // array[1] = 2;  <--- Can NOT remove.
  AddArraySet(entry_block_, array_, c1, c1);
  AddArraySet(entry_block_, array_, c2, c1);
  HInstruction* store1 = AddArraySet(entry_block_, array_, c1, c1);
  HInstruction* store2 = AddArraySet(entry_block_, array_, c1, c2);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(store1));
  ASSERT_FALSE(IsRemoved(store2));
}

TEST_F(LoadStoreEliminationTest, SameHeapValue2) {
  CreateTestControlFlowGraph();

  // Test LSE handling same value stores on vector.
  // vdata = [0x1, 0x2, 0x3, 0x4, ...]
  // VecStore array[i...] = vdata;
  // VecStore array[j...] = vdata;  <--- MAY ALIAS.
  // VecStore array[i...] = vdata;  <--- Cannot Remove, even if it's same value.
  AddVecStore(entry_block_, array_, i_);
  AddVecStore(entry_block_, array_, j_);
  HInstruction* vstore = AddVecStore(entry_block_, array_, i_);

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vstore));
}

TEST_F(LoadStoreEliminationTest, SameHeapValue3) {
  CreateTestControlFlowGraph();

  // VecStore array[i...] = vdata;
  // VecStore array[i+1...] = vdata;  <--- MAY alias due to partial overlap.
  // VecStore array[i...] = vdata;    <--- Cannot remove, even if it's same value.
  AddVecStore(entry_block_, array_, i_);
  AddVecStore(entry_block_, array_, i_add1_);
  HInstruction* vstore = AddVecStore(entry_block_, array_, i_);

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vstore));
}

TEST_F(LoadStoreEliminationTest, OverlappingLoadStore) {
  CreateTestControlFlowGraph();

  HInstruction* c1 = graph_->GetIntConstant(1);

  // Test LSE handling array LSE when there is vector store in between.
  // a[i] = 1;
  // .. = a[i];                <-- Remove.
  // a[i,i+1,i+2,i+3] = data;  <-- PARTIAL OVERLAP !
  // .. = a[i];                <-- Cannot remove.
  AddArraySet(entry_block_, array_, i_, c1);
  HInstruction* load1 = AddArrayGet(entry_block_, array_, i_);
  AddVecStore(entry_block_, array_, i_);
  HInstruction* load2 = AddArrayGet(entry_block_, array_, i_);

  // Test LSE handling vector load/store partial overlap.
  // a[i,i+1,i+2,i+3] = data;
  // a[i+4,i+5,i+6,i+7] = data;
  // .. = a[i,i+1,i+2,i+3];
  // .. = a[i+4,i+5,i+6,i+7];
  // a[i+1,i+2,i+3,i+4] = data;  <-- PARTIAL OVERLAP !
  // .. = a[i,i+1,i+2,i+3];
  // .. = a[i+4,i+5,i+6,i+7];
  AddVecStore(entry_block_, array_, i_);
  AddVecStore(entry_block_, array_, i_add4_);
  HInstruction* vload1 = AddVecLoad(entry_block_, array_, i_);
  HInstruction* vload2 = AddVecLoad(entry_block_, array_, i_add4_);
  AddVecStore(entry_block_, array_, i_add1_);
  HInstruction* vload3 = AddVecLoad(entry_block_, array_, i_);
  HInstruction* vload4 = AddVecLoad(entry_block_, array_, i_add4_);

  // Test LSE handling vector LSE when there is array store in between.
  // a[i,i+1,i+2,i+3] = data;
  // a[i+1] = 1;                 <-- PARTIAL OVERLAP !
  // .. = a[i,i+1,i+2,i+3];
  AddVecStore(entry_block_, array_, i_);
  AddArraySet(entry_block_, array_, i_, c1);
  HInstruction* vload5 = AddVecLoad(entry_block_, array_, i_);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(load1));
  ASSERT_FALSE(IsRemoved(load2));

  ASSERT_TRUE(IsRemoved(vload1));
  ASSERT_TRUE(IsRemoved(vload2));
  ASSERT_FALSE(IsRemoved(vload3));
  ASSERT_FALSE(IsRemoved(vload4));

  ASSERT_FALSE(IsRemoved(vload5));
}
// function (int[] a, int j) {
// a[j] = 1;
// for (int i=0; i<128; i++) {
//    /* doesn't do any write */
// }
// a[j] = 1;
TEST_F(LoadStoreEliminationTest, StoreAfterLoopWithoutSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c1 = graph_->GetIntConstant(1);

  // a[j] = 1
  AddArraySet(pre_header_, array_, j_, c1);

  // LOOP BODY:
  // .. = a[i,i+1,i+2,i+3];
  AddVecLoad(loop_, array_, phi_);

  // a[j] = 1;
  HInstruction* array_set = AddArraySet(return_block_, array_, j_, c1);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(array_set));
}

// function (int[] a, int j) {
//   int[] b = new int[128];
//   a[j] = 0;
//   for (int phi=0; phi<128; phi++) {
//     a[phi,phi+1,phi+2,phi+3] = [1,1,1,1];
//     b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3];
//   }
//   a[j] = 0;
// }
TEST_F(LoadStoreEliminationTest, StoreAfterSIMDLoopWithSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction());
  array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // a[j] = 0;
  AddArraySet(pre_header_, array_, j_, c0);

  // LOOP BODY:
  // a[phi,phi+1,phi+2,phi+3] = [1,1,1,1];
  // b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3];
  AddVecStore(loop_, array_, phi_);
  HInstruction* vload = AddVecLoad(loop_, array_, phi_);
  AddVecStore(loop_, array_b, phi_, vload->AsVecLoad());

  // a[j] = 0;
  HInstruction* a_set = AddArraySet(return_block_, array_, j_, c0);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(vload));
  ASSERT_FALSE(IsRemoved(a_set));  // Cannot remove due to write side-effect in the loop.
}

// function (int[] a, int j) {
//   int[] b = new int[128];
//   a[j] = 0;
//   for (int phi=0; phi<128; phi++) {
//     a[phi,phi+1,phi+2,phi+3] = [1,1,1,1];
//     b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3];
//   }
//   x = a[j];
// }
TEST_F(LoadStoreEliminationTest, LoadAfterSIMDLoopWithSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction());
  array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // a[j] = 0;
  AddArraySet(pre_header_, array_, j_, c0);

  // LOOP BODY:
  // a[phi,phi+1,phi+2,phi+3] = [1,1,1,1];
  // b[phi,phi+1,phi+2,phi+3] = a[phi,phi+1,phi+2,phi+3];
  AddVecStore(loop_, array_, phi_);
  HInstruction* vload = AddVecLoad(loop_, array_, phi_);
  AddVecStore(loop_, array_b, phi_, vload->AsVecLoad());

  // x = a[j];
  HInstruction* load = AddArrayGet(return_block_, array_, j_);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(vload));
  ASSERT_FALSE(IsRemoved(load));  // Cannot remove due to write side-effect in the loop.
}

// Check that merging works correctly when there are VecStors in predecessors.
//
//                  vstore1: a[i,... i + 3] = [1,...1]
//                       /          \
//                      /            \
// vstore2: a[i,... i + 3] = [1,...1]  vstore3: a[i+1, ... i + 4] = [1, ... 1]
//                     \              /
//                      \            /
//                  vstore4: a[i,... i + 3] = [1,...1]
//
// Expected:
//   'vstore2' is removed.
//   'vstore3' is not removed.
//   'vstore4' is not removed. Such cases are not supported at the moment.
TEST_F(LoadStoreEliminationTest, MergePredecessorVecStores) {
  HBasicBlock* upper;
  HBasicBlock* left;
  HBasicBlock* right;
  HBasicBlock* down;
  std::tie(upper, left, right, down) = CreateDiamondShapedCFG();

  // upper: a[i,... i + 3] = [1,...1]
  HInstruction* vstore1 = AddVecStore(upper, array_, i_);
  HInstruction* vdata = vstore1->InputAt(2);

  // left: a[i,... i + 3] = [1,...1]
  HInstruction* vstore2 = AddVecStore(left, array_, i_, vdata);

  // right: a[i+1, ... i + 4] = [1, ... 1]
  HInstruction* vstore3 = AddVecStore(right, array_, i_add1_, vdata);

  // down: a[i,... i + 3] = [1,...1]
  HInstruction* vstore4 = AddVecStore(down, array_, i_, vdata);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(vstore2));
  ASSERT_FALSE(IsRemoved(vstore3));
  ASSERT_FALSE(IsRemoved(vstore4));
}

// Check that merging works correctly when there are ArraySets in predecessors.
//
//          a[i] = 1
//        /          \
//       /            \
// store1: a[i] = 1  store2: a[i+1] = 1
//       \            /
//        \          /
//          store3: a[i] = 1
//
// Expected:
//   'store1' is removed.
//   'store2' is not removed.
//   'store3' is removed.
TEST_F(LoadStoreEliminationTest, MergePredecessorStores) {
  HBasicBlock* upper;
  HBasicBlock* left;
  HBasicBlock* right;
  HBasicBlock* down;
  std::tie(upper, left, right, down) = CreateDiamondShapedCFG();

  // upper: a[i,... i + 3] = [1,...1]
  AddArraySet(upper, array_, i_);

  // left: a[i,... i + 3] = [1,...1]
  HInstruction* store1 = AddArraySet(left, array_, i_);

  // right: a[i+1, ... i + 4] = [1, ... 1]
  HInstruction* store2 = AddArraySet(right, array_, i_add1_);

  // down: a[i,... i + 3] = [1,...1]
  HInstruction* store3 = AddArraySet(down, array_, i_);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(store1));
  ASSERT_FALSE(IsRemoved(store2));
  ASSERT_TRUE(IsRemoved(store3));
}

// Check that redundant VStore/VLoad are removed from a SIMD loop.
//
//  LOOP BODY
//     vstore1: a[i,... i + 3] = [1,...1]
//     vload:   x = a[i,... i + 3]
//     vstore2: b[i,... i + 3] = x
//     vstore3: a[i,... i + 3] = [1,...1]
//
// Return 'a' from the method to make it escape.
//
// Expected:
//   'vstore1' is not removed.
//   'vload' is removed.
//   'vstore2' is removed because 'b' does not escape.
//   'vstore3' is removed.
TEST_F(LoadStoreEliminationTest, RedundantVStoreVLoadInLoop) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  ASSERT_TRUE(return_block_->GetLastInstruction()->IsReturnVoid());
  HInstruction* ret = new (GetAllocator()) HReturn(array_a);
  return_block_->ReplaceAndRemoveInstructionWith(return_block_->GetLastInstruction(), ret);

  HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction());
  array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // LOOP BODY:
  //    a[i,... i + 3] = [1,...1]
  //    x = a[i,... i + 3]
  //    b[i,... i + 3] = x
  //    a[i,... i + 3] = [1,...1]
  HInstruction* vstore1 = AddVecStore(loop_, array_a, phi_);
  HInstruction* vload = AddVecLoad(loop_, array_a, phi_);
  HInstruction* vstore2 = AddVecStore(loop_, array_b, phi_, vload->AsVecLoad());
  HInstruction* vstore3 = AddVecStore(loop_, array_a, phi_, vstore1->InputAt(2));

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vstore1));
  ASSERT_TRUE(IsRemoved(vload));
  ASSERT_TRUE(IsRemoved(vstore2));
  ASSERT_TRUE(IsRemoved(vstore3));
}

// Loop writes invalidate only possibly aliased heap locations.
TEST_F(LoadStoreEliminationTest, StoreAfterLoopWithSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c128 = graph_->GetIntConstant(128);

  // array[0] = 2;
  // loop:
  //   b[i] = array[i]
  // array[0] = 2
  HInstruction* store1 = AddArraySet(entry_block_, array_, c0, c2);

  HInstruction* array_b = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_b, pre_header_->GetLastInstruction());
  array_b->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  HInstruction* load = AddArrayGet(loop_, array_, phi_);
  HInstruction* store2 = AddArraySet(loop_, array_b, phi_, load);

  HInstruction* store3 = AddArraySet(return_block_, array_, c0, c2);

  PerformLSE();

  ASSERT_FALSE(IsRemoved(store1));
  ASSERT_TRUE(IsRemoved(store2));
  ASSERT_TRUE(IsRemoved(store3));
}

// Loop writes invalidate only possibly aliased heap locations.
TEST_F(LoadStoreEliminationTest, StoreAfterLoopWithSideEffects2) {
  CreateTestControlFlowGraph();

  // Add another array parameter that may alias with `array_`.
  // Note: We're not adding it to the suspend check environment.
  AddParameter(new (GetAllocator()) HParameterValue(
      graph_->GetDexFile(), dex::TypeIndex(0), 3, DataType::Type::kInt32));
  HInstruction* array2 = parameters_.back();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c2 = graph_->GetIntConstant(2);

  // array[0] = 2;
  // loop:
  //   array2[i] = array[i]
  // array[0] = 2
  HInstruction* store1 = AddArraySet(entry_block_, array_, c0, c2);

  HInstruction* load = AddArrayGet(loop_, array_, phi_);
  HInstruction* store2 = AddArraySet(loop_, array2, phi_, load);

  HInstruction* store3 = AddArraySet(return_block_, array_, c0, c2);

  PerformLSE();

  ASSERT_FALSE(IsRemoved(store1));
  ASSERT_FALSE(IsRemoved(store2));
  ASSERT_FALSE(IsRemoved(store3));
}

// As it is not allowed to use defaults for VecLoads, check if there is a new created array
// a VecLoad used in a loop and after it is not replaced with a default.
TEST_F(LoadStoreEliminationTest, VLoadDefaultValueInLoopWithoutWriteSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // LOOP BODY:
  //    v = a[i,... i + 3]
  // array[0,... 3] = v
  HInstruction* vload = AddVecLoad(loop_, array_a, phi_);
  HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vload));
  ASSERT_FALSE(IsRemoved(vstore));
}

// As it is not allowed to use defaults for VecLoads, check if there is a new created array
// a VecLoad is not replaced with a default.
TEST_F(LoadStoreEliminationTest, VLoadDefaultValue) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // v = a[0,... 3]
  // array[0,... 3] = v
  HInstruction* vload = AddVecLoad(pre_header_, array_a, c0);
  HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vload));
  ASSERT_FALSE(IsRemoved(vstore));
}

// As it is allowed to use defaults for ordinary loads, check if there is a new created array
// a load used in a loop and after it is replaced with a default.
TEST_F(LoadStoreEliminationTest, LoadDefaultValueInLoopWithoutWriteSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // LOOP BODY:
  //    v = a[i]
  // array[0] = v
  HInstruction* load = AddArrayGet(loop_, array_a, phi_);
  HInstruction* store = AddArraySet(return_block_, array_, c0, load);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(load));
  ASSERT_FALSE(IsRemoved(store));
}

// As it is allowed to use defaults for ordinary loads, check if there is a new created array
// a load is replaced with a default.
TEST_F(LoadStoreEliminationTest, LoadDefaultValue) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // v = a[0]
  // array[0] = v
  HInstruction* load = AddArrayGet(pre_header_, array_a, c0);
  HInstruction* store = AddArraySet(return_block_, array_, c0, load);

  PerformLSE();

  ASSERT_TRUE(IsRemoved(load));
  ASSERT_FALSE(IsRemoved(store));
}

// As it is not allowed to use defaults for VecLoads but allowed for regular loads,
// check if there is a new created array, a VecLoad and a load used in a loop and after it,
// VecLoad is not replaced with a default but the load is.
TEST_F(LoadStoreEliminationTest, VLoadAndLoadDefaultValueInLoopWithoutWriteSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // LOOP BODY:
  //    v = a[i,... i + 3]
  //    v1 = a[i]
  // array[0,... 3] = v
  // array[0] = v1
  HInstruction* vload = AddVecLoad(loop_, array_a, phi_);
  HInstruction* load = AddArrayGet(loop_, array_a, phi_);
  HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());
  HInstruction* store = AddArraySet(return_block_, array_, c0, load);

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vload));
  ASSERT_TRUE(IsRemoved(load));
  ASSERT_FALSE(IsRemoved(vstore));
  ASSERT_FALSE(IsRemoved(store));
}

// As it is not allowed to use defaults for VecLoads but allowed for regular loads,
// check if there is a new created array, a VecLoad and a load,
// VecLoad is not replaced with a default but the load is.
TEST_F(LoadStoreEliminationTest, VLoadAndLoadDefaultValue) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // v = a[0,... 3]
  // v1 = a[0]
  // array[0,... 3] = v
  // array[0] = v1
  HInstruction* vload = AddVecLoad(pre_header_, array_a, c0);
  HInstruction* load = AddArrayGet(pre_header_, array_a, c0);
  HInstruction* vstore = AddVecStore(return_block_, array_, c0, vload->AsVecLoad());
  HInstruction* store = AddArraySet(return_block_, array_, c0, load);

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vload));
  ASSERT_TRUE(IsRemoved(load));
  ASSERT_FALSE(IsRemoved(vstore));
  ASSERT_FALSE(IsRemoved(store));
}

// It is not allowed to use defaults for VecLoads. However it should not prevent from removing
// loads getting the same value.
// Check a load getting a known value is eliminated (a loop test case).
TEST_F(LoadStoreEliminationTest, VLoadDefaultValueAndVLoadInLoopWithoutWriteSideEffects) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // LOOP BODY:
  //    v = a[i,... i + 3]
  //    v1 = a[i,... i + 3]
  // array[0,... 3] = v
  // array[128,... 131] = v1
  HInstruction* vload1 = AddVecLoad(loop_, array_a, phi_);
  HInstruction* vload2 = AddVecLoad(loop_, array_a, phi_);
  HInstruction* vstore1 = AddVecStore(return_block_, array_, c0, vload1->AsVecLoad());
  HInstruction* vstore2 = AddVecStore(return_block_, array_, c128, vload2->AsVecLoad());

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vload1));
  ASSERT_TRUE(IsRemoved(vload2));
  ASSERT_FALSE(IsRemoved(vstore1));
  ASSERT_FALSE(IsRemoved(vstore2));
}

// It is not allowed to use defaults for VecLoads. However it should not prevent from removing
// loads getting the same value.
// Check a load getting a known value is eliminated.
TEST_F(LoadStoreEliminationTest, VLoadDefaultValueAndVLoad) {
  CreateTestControlFlowGraph();

  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c128 = graph_->GetIntConstant(128);

  HInstruction* array_a = new (GetAllocator()) HNewArray(c0, c128, 0, 0);
  pre_header_->InsertInstructionBefore(array_a, pre_header_->GetLastInstruction());
  array_a->CopyEnvironmentFrom(suspend_check_->GetEnvironment());

  // v = a[0,... 3]
  // v1 = a[0,... 3]
  // array[0,... 3] = v
  // array[128,... 131] = v1
  HInstruction* vload1 = AddVecLoad(pre_header_, array_a, c0);
  HInstruction* vload2 = AddVecLoad(pre_header_, array_a, c0);
  HInstruction* vstore1 = AddVecStore(return_block_, array_, c0, vload1->AsVecLoad());
  HInstruction* vstore2 = AddVecStore(return_block_, array_, c128, vload2->AsVecLoad());

  PerformLSE();

  ASSERT_FALSE(IsRemoved(vload1));
  ASSERT_TRUE(IsRemoved(vload2));
  ASSERT_FALSE(IsRemoved(vstore1));
  ASSERT_FALSE(IsRemoved(vstore2));
}

// Object o = new Obj();
// // Needed because otherwise we short-circuit LSA since GVN would get almost
// // everything other than this. Also since this isn't expected to be a very
// // common pattern it's not worth changing the LSA logic.
// o.foo = 3;
// return o.shadow$_klass_;
TEST_F(LoadStoreEliminationTest, DefaultShadowClass) {
  CreateGraph();
  AdjacencyListGraph blocks(
      graph_, GetAllocator(), "entry", "exit", {{"entry", "main"}, {"main", "exit"}});
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(main);
  GET_BLOCK(exit);
#undef GET_BLOCK

  HInstruction* suspend_check = new (GetAllocator()) HSuspendCheck();
  entry->AddInstruction(suspend_check);
  entry->AddInstruction(new (GetAllocator()) HGoto());
  ManuallyBuildEnvFor(suspend_check, {});

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* const_fence = new (GetAllocator()) HConstructorFence(new_inst, 0, GetAllocator());
  HInstruction* set_field = MakeIFieldSet(new_inst, graph_->GetIntConstant(33), MemberOffset(32));
  HInstruction* get_field =
      MakeIFieldGet(new_inst, DataType::Type::kReference, mirror::Object::ClassOffset());
  HInstruction* return_val = new (GetAllocator()) HReturn(get_field);
  main->AddInstruction(cls);
  main->AddInstruction(new_inst);
  main->AddInstruction(const_fence);
  main->AddInstruction(set_field);
  main->AddInstruction(get_field);
  main->AddInstruction(return_val);
  cls->CopyEnvironmentFrom(suspend_check->GetEnvironment());
  new_inst->CopyEnvironmentFrom(suspend_check->GetEnvironment());

  SetupExit(exit);

  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(new_inst);
  EXPECT_INS_REMOVED(const_fence);
  EXPECT_INS_REMOVED(get_field);
  EXPECT_INS_REMOVED(set_field);
  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_EQ(cls, return_val->InputAt(0));
}

// Object o = new Obj();
// // Needed because otherwise we short-circuit LSA since GVN would get almost
// // everything other than this. Also since this isn't expected to be a very
// // common pattern (only a single java function, Object.identityHashCode,
// // ever reads this field) it's not worth changing the LSA logic.
// o.foo = 3;
// return o.shadow$_monitor_;
TEST_F(LoadStoreEliminationTest, DefaultShadowMonitor) {
  CreateGraph();
  AdjacencyListGraph blocks(
      graph_, GetAllocator(), "entry", "exit", {{"entry", "main"}, {"main", "exit"}});
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(main);
  GET_BLOCK(exit);
#undef GET_BLOCK

  HInstruction* suspend_check = new (GetAllocator()) HSuspendCheck();
  entry->AddInstruction(suspend_check);
  entry->AddInstruction(new (GetAllocator()) HGoto());
  ManuallyBuildEnvFor(suspend_check, {});

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* const_fence = new (GetAllocator()) HConstructorFence(new_inst, 0, GetAllocator());
  HInstruction* set_field = MakeIFieldSet(new_inst, graph_->GetIntConstant(33), MemberOffset(32));
  HInstruction* get_field =
      MakeIFieldGet(new_inst, DataType::Type::kInt32, mirror::Object::MonitorOffset());
  HInstruction* return_val = new (GetAllocator()) HReturn(get_field);
  main->AddInstruction(cls);
  main->AddInstruction(new_inst);
  main->AddInstruction(const_fence);
  main->AddInstruction(set_field);
  main->AddInstruction(get_field);
  main->AddInstruction(return_val);
  cls->CopyEnvironmentFrom(suspend_check->GetEnvironment());
  new_inst->CopyEnvironmentFrom(suspend_check->GetEnvironment());

  SetupExit(exit);

  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(new_inst);
  EXPECT_INS_REMOVED(const_fence);
  EXPECT_INS_REMOVED(get_field);
  EXPECT_INS_REMOVED(set_field);
  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_EQ(graph_->GetIntConstant(0), return_val->InputAt(0));
}

// void DO_CAL() {
//   int i = 1;
//   int[] w = new int[80];
//   int t = 0;
//   while (i < 80) {
//     w[i] = PLEASE_INTERLEAVE(w[i - 1], 1)
//     t = PLEASE_SELECT(w[i], t);
//     i++;
//   }
//   return t;
// }
TEST_F(LoadStoreEliminationTest, ArrayLoopOverlap) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blocks(graph_,
                            GetAllocator(),
                            "entry",
                            "exit",
                            { { "entry", "loop_pre_header" },
                              { "loop_pre_header", "loop_entry" },
                              { "loop_entry", "loop_body" },
                              { "loop_entry", "loop_post" },
                              { "loop_body", "loop_entry" },
                              { "loop_post", "exit" } });
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_entry);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_post);
  GET_BLOCK(exit);
#undef GET_BLOCK

  HInstruction* zero_const = graph_->GetConstant(DataType::Type::kInt32, 0);
  HInstruction* one_const = graph_->GetConstant(DataType::Type::kInt32, 1);
  HInstruction* eighty_const = graph_->GetConstant(DataType::Type::kInt32, 80);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(entry_goto);

  HInstruction* alloc_w = new (GetAllocator()) HNewArray(zero_const, eighty_const, 0, 0);
  HInstruction* pre_header_goto = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(alloc_w);
  loop_pre_header->AddInstruction(pre_header_goto);
  // environment
  ManuallyBuildEnvFor(alloc_w, {});

  // loop-start
  HPhi* i_phi = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32);
  HPhi* t_phi = new (GetAllocator()) HPhi(GetAllocator(), 1, 0, DataType::Type::kInt32);
  HInstruction* suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* i_cmp_top = new (GetAllocator()) HGreaterThanOrEqual(i_phi, eighty_const);
  HInstruction* loop_start_branch = new (GetAllocator()) HIf(i_cmp_top);
  loop_entry->AddPhi(i_phi);
  loop_entry->AddPhi(t_phi);
  loop_entry->AddInstruction(suspend);
  loop_entry->AddInstruction(i_cmp_top);
  loop_entry->AddInstruction(loop_start_branch);
  CHECK_EQ(loop_entry->GetSuccessors().size(), 2u);
  if (loop_entry->GetNormalSuccessors()[1] != loop_body) {
    loop_entry->SwapSuccessors();
  }
  CHECK_EQ(loop_entry->GetPredecessors().size(), 2u);
  if (loop_entry->GetPredecessors()[0] != loop_pre_header) {
    loop_entry->SwapPredecessors();
  }
  i_phi->AddInput(one_const);
  t_phi->AddInput(zero_const);

  // environment
  ManuallyBuildEnvFor(suspend, { alloc_w, i_phi, t_phi });

  // BODY
  HInstruction* last_i = new (GetAllocator()) HSub(DataType::Type::kInt32, i_phi, one_const);
  HInstruction* last_get =
      new (GetAllocator()) HArrayGet(alloc_w, last_i, DataType::Type::kInt32, 0);
  HInvoke* body_value = MakeInvoke(DataType::Type::kInt32, { last_get, one_const });
  HInstruction* body_set =
      new (GetAllocator()) HArraySet(alloc_w, i_phi, body_value, DataType::Type::kInt32, 0);
  HInstruction* body_get =
      new (GetAllocator()) HArrayGet(alloc_w, i_phi, DataType::Type::kInt32, 0);
  HInvoke* t_next = MakeInvoke(DataType::Type::kInt32, { body_get, t_phi });
  HInstruction* i_next = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_phi, one_const);
  HInstruction* body_goto = new (GetAllocator()) HGoto();
  loop_body->AddInstruction(last_i);
  loop_body->AddInstruction(last_get);
  loop_body->AddInstruction(body_value);
  loop_body->AddInstruction(body_set);
  loop_body->AddInstruction(body_get);
  loop_body->AddInstruction(t_next);
  loop_body->AddInstruction(i_next);
  loop_body->AddInstruction(body_goto);
  body_value->CopyEnvironmentFrom(suspend->GetEnvironment());

  i_phi->AddInput(i_next);
  t_phi->AddInput(t_next);
  t_next->CopyEnvironmentFrom(suspend->GetEnvironment());

  // loop-post
  HInstruction* return_inst = new (GetAllocator()) HReturn(t_phi);
  loop_post->AddInstruction(return_inst);

  // exit
  SetupExit(exit);

  graph_->ClearDominanceInformation();
  graph_->ClearLoopInformation();
  PerformLSE();

  // TODO Technically this is optimizable. LSE just needs to add phis to keep
  // track of the last `N` values set where `N` is how many locations we can go
  // back into the array.
  if (IsRemoved(last_get)) {
    // If we were able to remove the previous read the entire array should be removable.
    EXPECT_INS_REMOVED(body_set);
    EXPECT_INS_REMOVED(alloc_w);
  } else {
    // This is the branch we actually take for now. If we rely on being able to
    // read the array we'd better remember to write to it as well.
    EXPECT_INS_RETAINED(body_set);
  }
  // The last 'get' should always be removable.
  EXPECT_INS_REMOVED(body_get);
}

// void DO_CAL2() {
//   int i = 1;
//   int[] w = new int[80];
//   int t = 0;
//   while (i < 80) {
//     w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) // <-- removed
//     t = PLEASE_SELECT(w[i], t);
//     w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) // <-- removed
//     t = PLEASE_SELECT(w[i], t);
//     w[i] = PLEASE_INTERLEAVE(w[i - 1], 1) // <-- kept
//     t = PLEASE_SELECT(w[i], t);
//     i++;
//   }
//   return t;
// }
TEST_F(LoadStoreEliminationTest, ArrayLoopOverlap2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blocks(graph_,
                            GetAllocator(),
                            "entry",
                            "exit",
                            { { "entry", "loop_pre_header" },
                              { "loop_pre_header", "loop_entry" },
                              { "loop_entry", "loop_body" },
                              { "loop_entry", "loop_post" },
                              { "loop_body", "loop_entry" },
                              { "loop_post", "exit" } });
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_entry);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_post);
  GET_BLOCK(exit);
#undef GET_BLOCK

  HInstruction* zero_const = graph_->GetConstant(DataType::Type::kInt32, 0);
  HInstruction* one_const = graph_->GetConstant(DataType::Type::kInt32, 1);
  HInstruction* eighty_const = graph_->GetConstant(DataType::Type::kInt32, 80);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(entry_goto);

  HInstruction* alloc_w = new (GetAllocator()) HNewArray(zero_const, eighty_const, 0, 0);
  HInstruction* pre_header_goto = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(alloc_w);
  loop_pre_header->AddInstruction(pre_header_goto);
  // environment
  ManuallyBuildEnvFor(alloc_w, {});

  // loop-start
  HPhi* i_phi = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32);
  HPhi* t_phi = new (GetAllocator()) HPhi(GetAllocator(), 1, 0, DataType::Type::kInt32);
  HInstruction* suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* i_cmp_top = new (GetAllocator()) HGreaterThanOrEqual(i_phi, eighty_const);
  HInstruction* loop_start_branch = new (GetAllocator()) HIf(i_cmp_top);
  loop_entry->AddPhi(i_phi);
  loop_entry->AddPhi(t_phi);
  loop_entry->AddInstruction(suspend);
  loop_entry->AddInstruction(i_cmp_top);
  loop_entry->AddInstruction(loop_start_branch);
  CHECK_EQ(loop_entry->GetSuccessors().size(), 2u);
  if (loop_entry->GetNormalSuccessors()[1] != loop_body) {
    loop_entry->SwapSuccessors();
  }
  CHECK_EQ(loop_entry->GetPredecessors().size(), 2u);
  if (loop_entry->GetPredecessors()[0] != loop_pre_header) {
    loop_entry->SwapPredecessors();
  }
  i_phi->AddInput(one_const);
  t_phi->AddInput(zero_const);

  // environment
  ManuallyBuildEnvFor(suspend, { alloc_w, i_phi, t_phi });

  // BODY
  HInstruction* last_i = new (GetAllocator()) HSub(DataType::Type::kInt32, i_phi, one_const);
  HInstruction *last_get_1, *last_get_2, *last_get_3;
  HInstruction *body_value_1, *body_value_2, *body_value_3;
  HInstruction *body_set_1, *body_set_2, *body_set_3;
  HInstruction *body_get_1, *body_get_2, *body_get_3;
  HInstruction *t_next_1, *t_next_2, *t_next_3;
  auto make_instructions = [&](HInstruction* last_t_value) {
    HInstruction* last_get =
        new (GetAllocator()) HArrayGet(alloc_w, last_i, DataType::Type::kInt32, 0);
    HInvoke* body_value = MakeInvoke(DataType::Type::kInt32, { last_get, one_const });
    HInstruction* body_set =
        new (GetAllocator()) HArraySet(alloc_w, i_phi, body_value, DataType::Type::kInt32, 0);
    HInstruction* body_get =
        new (GetAllocator()) HArrayGet(alloc_w, i_phi, DataType::Type::kInt32, 0);
    HInvoke* t_next = MakeInvoke(DataType::Type::kInt32, { body_get, last_t_value });
    loop_body->AddInstruction(last_get);
    loop_body->AddInstruction(body_value);
    loop_body->AddInstruction(body_set);
    loop_body->AddInstruction(body_get);
    loop_body->AddInstruction(t_next);
    return std::make_tuple(last_get, body_value, body_set, body_get, t_next);
  };
  std::tie(last_get_1, body_value_1, body_set_1, body_get_1, t_next_1) = make_instructions(t_phi);
  std::tie(last_get_2, body_value_2, body_set_2, body_get_2, t_next_2) =
      make_instructions(t_next_1);
  std::tie(last_get_3, body_value_3, body_set_3, body_get_3, t_next_3) =
      make_instructions(t_next_2);
  HInstruction* i_next = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_phi, one_const);
  HInstruction* body_goto = new (GetAllocator()) HGoto();
  loop_body->InsertInstructionBefore(last_i, last_get_1);
  loop_body->AddInstruction(i_next);
  loop_body->AddInstruction(body_goto);
  body_value_1->CopyEnvironmentFrom(suspend->GetEnvironment());
  body_value_2->CopyEnvironmentFrom(suspend->GetEnvironment());
  body_value_3->CopyEnvironmentFrom(suspend->GetEnvironment());

  i_phi->AddInput(i_next);
  t_phi->AddInput(t_next_3);
  t_next_1->CopyEnvironmentFrom(suspend->GetEnvironment());
  t_next_2->CopyEnvironmentFrom(suspend->GetEnvironment());
  t_next_3->CopyEnvironmentFrom(suspend->GetEnvironment());

  // loop-post
  HInstruction* return_inst = new (GetAllocator()) HReturn(t_phi);
  loop_post->AddInstruction(return_inst);

  // exit
  SetupExit(exit);

  graph_->ClearDominanceInformation();
  graph_->ClearLoopInformation();
  PerformLSE();

  // TODO Technically this is optimizable. LSE just needs to add phis to keep
  // track of the last `N` values set where `N` is how many locations we can go
  // back into the array.
  if (IsRemoved(last_get_1)) {
    // If we were able to remove the previous read the entire array should be removable.
    EXPECT_INS_REMOVED(body_set_1);
    EXPECT_INS_REMOVED(body_set_2);
    EXPECT_INS_REMOVED(body_set_3);
    EXPECT_INS_REMOVED(last_get_1);
    EXPECT_INS_REMOVED(last_get_2);
    EXPECT_INS_REMOVED(alloc_w);
  } else {
    // This is the branch we actually take for now. If we rely on being able to
    // read the array we'd better remember to write to it as well.
    EXPECT_INS_RETAINED(body_set_3);
  }
  // The last 'get' should always be removable.
  EXPECT_INS_REMOVED(body_get_1);
  EXPECT_INS_REMOVED(body_get_2);
  EXPECT_INS_REMOVED(body_get_3);
  // shadowed writes should always be removed
  EXPECT_INS_REMOVED(body_set_1);
  EXPECT_INS_REMOVED(body_set_2);
}

TEST_F(LoadStoreEliminationTest, ArrayNonLoopPhi) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blocks(graph_,
                            GetAllocator(),
                            "entry",
                            "exit",
                            { { "entry", "start" },
                              { "start", "left" },
                              { "start", "right" },
                              { "left", "ret" },
                              { "right", "ret" },
                              { "ret", "exit" } });
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(start);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(ret);
  GET_BLOCK(exit);
#undef GET_BLOCK

  HInstruction* zero_const = graph_->GetConstant(DataType::Type::kInt32, 0);
  HInstruction* one_const = graph_->GetConstant(DataType::Type::kInt32, 1);
  HInstruction* two_const = graph_->GetConstant(DataType::Type::kInt32, 2);
  HInstruction* param = MakeParam(DataType::Type::kBool);

  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(entry_goto);

  HInstruction* alloc_w = new (GetAllocator()) HNewArray(zero_const, two_const, 0, 0);
  HInstruction* branch = new (GetAllocator()) HIf(param);
  start->AddInstruction(alloc_w);
  start->AddInstruction(branch);
  // environment
  ManuallyBuildEnvFor(alloc_w, {});

  // left
  HInvoke* left_value = MakeInvoke(DataType::Type::kInt32, { zero_const });
  HInstruction* left_set_1 =
      new (GetAllocator()) HArraySet(alloc_w, zero_const, left_value, DataType::Type::kInt32, 0);
  HInstruction* left_set_2 =
      new (GetAllocator()) HArraySet(alloc_w, one_const, zero_const, DataType::Type::kInt32, 0);
  HInstruction* left_goto = new (GetAllocator()) HGoto();
  left->AddInstruction(left_value);
  left->AddInstruction(left_set_1);
  left->AddInstruction(left_set_2);
  left->AddInstruction(left_goto);
  ManuallyBuildEnvFor(left_value, { alloc_w });

  // right
  HInvoke* right_value = MakeInvoke(DataType::Type::kInt32, { one_const });
  HInstruction* right_set_1 =
      new (GetAllocator()) HArraySet(alloc_w, zero_const, right_value, DataType::Type::kInt32, 0);
  HInstruction* right_set_2 =
      new (GetAllocator()) HArraySet(alloc_w, one_const, zero_const, DataType::Type::kInt32, 0);
  HInstruction* right_goto = new (GetAllocator()) HGoto();
  right->AddInstruction(right_value);
  right->AddInstruction(right_set_1);
  right->AddInstruction(right_set_2);
  right->AddInstruction(right_goto);
  ManuallyBuildEnvFor(right_value, { alloc_w });

  // ret
  HInstruction* read_1 =
      new (GetAllocator()) HArrayGet(alloc_w, zero_const, DataType::Type::kInt32, 0);
  HInstruction* read_2 =
      new (GetAllocator()) HArrayGet(alloc_w, one_const, DataType::Type::kInt32, 0);
  HInstruction* add = new (GetAllocator()) HAdd(DataType::Type::kInt32, read_1, read_2);
  HInstruction* return_inst = new (GetAllocator()) HReturn(add);
  ret->AddInstruction(read_1);
  ret->AddInstruction(read_2);
  ret->AddInstruction(add);
  ret->AddInstruction(return_inst);

  // exit
  SetupExit(exit);

  graph_->ClearDominanceInformation();
  graph_->ClearLoopInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(read_1);
  EXPECT_INS_REMOVED(read_2);
  EXPECT_INS_REMOVED(left_set_1);
  EXPECT_INS_REMOVED(left_set_2);
  EXPECT_INS_REMOVED(right_set_1);
  EXPECT_INS_REMOVED(right_set_2);
  EXPECT_INS_REMOVED(alloc_w);

  EXPECT_INS_RETAINED(left_value);
  EXPECT_INS_RETAINED(right_value);
}

TEST_F(LoadStoreEliminationTest, ArrayMergeDefault) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blocks(graph_,
                            GetAllocator(),
                            "entry",
                            "exit",
                            { { "entry", "start" },
                              { "start", "left" },
                              { "start", "right" },
                              { "left", "ret" },
                              { "right", "ret" },
                              { "ret", "exit" } });
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(start);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(ret);
  GET_BLOCK(exit);
#undef GET_BLOCK

  HInstruction* zero_const = graph_->GetConstant(DataType::Type::kInt32, 0);
  HInstruction* one_const = graph_->GetConstant(DataType::Type::kInt32, 1);
  HInstruction* two_const = graph_->GetConstant(DataType::Type::kInt32, 2);
  HInstruction* param = MakeParam(DataType::Type::kBool);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();

  entry->AddInstruction(entry_goto);

  HInstruction* alloc_w = new (GetAllocator()) HNewArray(zero_const, two_const, 0, 0);
  HInstruction* branch = new (GetAllocator()) HIf(param);
  start->AddInstruction(alloc_w);
  start->AddInstruction(branch);
  // environment
  ArenaVector<HInstruction*> alloc_locals({}, GetAllocator()->Adapter(kArenaAllocInstruction));
  ManuallyBuildEnvFor(alloc_w, {});

  // left
  HInstruction* left_set_1 =
      new (GetAllocator()) HArraySet(alloc_w, zero_const, one_const, DataType::Type::kInt32, 0);
  HInstruction* left_set_2 =
      new (GetAllocator()) HArraySet(alloc_w, zero_const, zero_const, DataType::Type::kInt32, 0);
  HInstruction* left_goto = new (GetAllocator()) HGoto();
  left->AddInstruction(left_set_1);
  left->AddInstruction(left_set_2);
  left->AddInstruction(left_goto);

  // right
  HInstruction* right_set_1 =
      new (GetAllocator()) HArraySet(alloc_w, one_const, one_const, DataType::Type::kInt32, 0);
  HInstruction* right_set_2 =
      new (GetAllocator()) HArraySet(alloc_w, one_const, zero_const, DataType::Type::kInt32, 0);
  HInstruction* right_goto = new (GetAllocator()) HGoto();
  right->AddInstruction(right_set_1);
  right->AddInstruction(right_set_2);
  right->AddInstruction(right_goto);

  // ret
  HInstruction* read_1 =
      new (GetAllocator()) HArrayGet(alloc_w, zero_const, DataType::Type::kInt32, 0);
  HInstruction* read_2 =
      new (GetAllocator()) HArrayGet(alloc_w, one_const, DataType::Type::kInt32, 0);
  HInstruction* add = new (GetAllocator()) HAdd(DataType::Type::kInt32, read_1, read_2);
  HInstruction* return_inst = new (GetAllocator()) HReturn(add);
  ret->AddInstruction(read_1);
  ret->AddInstruction(read_2);
  ret->AddInstruction(add);
  ret->AddInstruction(return_inst);

  // exit
  SetupExit(exit);

  graph_->ClearDominanceInformation();
  graph_->ClearLoopInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(read_1);
  EXPECT_INS_REMOVED(read_2);
  EXPECT_INS_REMOVED(left_set_1);
  EXPECT_INS_REMOVED(left_set_2);
  EXPECT_INS_REMOVED(right_set_1);
  EXPECT_INS_REMOVED(right_set_2);
  EXPECT_INS_REMOVED(alloc_w);
}

// Regression test for b/187487955.
// We previusly failed to consider aliasing between an array location
// with index `idx` defined in the loop (such as a loop Phi) and another
// array location with index `idx + constant`. This could have led to
// replacing the load with, for example, the default value 0.
TEST_F(LoadStoreEliminationTest, ArrayLoopAliasing1) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blocks(graph_,
                            GetAllocator(),
                            "entry",
                            "exit",
                            { { "entry", "preheader" },
                              { "preheader", "loop" },
                              { "loop", "body" },
                              { "body", "loop" },
                              { "loop", "ret" },
                              { "ret", "exit" } });
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(preheader);
  GET_BLOCK(loop);
  GET_BLOCK(body);
  GET_BLOCK(ret);
  GET_BLOCK(exit);
#undef GET_BLOCK
  HInstruction* n = MakeParam(DataType::Type::kInt32);
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c1 = graph_->GetIntConstant(1);

  // entry
  HInstruction* cls = MakeClassLoad();
  HInstruction* array = new (GetAllocator()) HNewArray(
      cls, n, /*dex_pc=*/ 0u, DataType::SizeShift(DataType::Type::kInt32));
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(array);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  ManuallyBuildEnvFor(array, {});

  HInstruction* preheader_goto = new (GetAllocator()) HGoto();
  preheader->AddInstruction(preheader_goto);

  // loop
  HPhi* i_phi = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32);
  HInstruction* loop_suspend_check = new (GetAllocator()) HSuspendCheck();
  HInstruction* loop_cond = new (GetAllocator()) HLessThan(i_phi, n);
  HIf* loop_if = new (GetAllocator()) HIf(loop_cond);
  loop->AddPhi(i_phi);
  loop->AddInstruction(loop_suspend_check);
  loop->AddInstruction(loop_cond);
  loop->AddInstruction(loop_if);
  CHECK(loop_if->IfTrueSuccessor() == body);
  ManuallyBuildEnvFor(loop_suspend_check, {});

  // body
  HInstruction* body_set =
      new (GetAllocator()) HArraySet(array, i_phi, i_phi, DataType::Type::kInt32, /*dex_pc=*/ 0u);
  HInstruction* body_add = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_phi, c1);
  HInstruction* body_goto = new (GetAllocator()) HGoto();
  body->AddInstruction(body_set);
  body->AddInstruction(body_add);
  body->AddInstruction(body_goto);

  // i_phi inputs
  i_phi->AddInput(c0);
  i_phi->AddInput(body_add);

  // ret
  HInstruction* ret_sub = new (GetAllocator()) HSub(DataType::Type::kInt32, i_phi, c1);
  HInstruction* ret_get =
      new (GetAllocator()) HArrayGet(array, ret_sub, DataType::Type::kInt32, /*dex_pc=*/ 0);
  HInstruction* ret_return = new (GetAllocator()) HReturn(ret_get);
  ret->AddInstruction(ret_sub);
  ret->AddInstruction(ret_get);
  ret->AddInstruction(ret_return);

  // exit
  SetupExit(exit);

  graph_->ClearDominanceInformation();
  graph_->ClearLoopInformation();
  PerformLSE();

  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_RETAINED(array);
  EXPECT_INS_RETAINED(body_set);
  EXPECT_INS_RETAINED(ret_get);
}

// Regression test for b/187487955.
// Similar to the `ArrayLoopAliasing1` test above but with additional load
// that marks a loop Phi placeholder as kept which used to trigger a DCHECK().
// There is also an LSE run-test for this but it relies on BCE eliminating
// BoundsCheck instructions and adds extra code in loop body to avoid
// loop unrolling. This gtest does not need to jump through those hoops
// as we do not unnecessarily run those optimization passes.
TEST_F(LoadStoreEliminationTest, ArrayLoopAliasing2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blocks(graph_,
                            GetAllocator(),
                            "entry",
                            "exit",
                            { { "entry", "preheader" },
                              { "preheader", "loop" },
                              { "loop", "body" },
                              { "body", "loop" },
                              { "loop", "ret" },
                              { "ret", "exit" } });
#define GET_BLOCK(name) HBasicBlock* name = blocks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(preheader);
  GET_BLOCK(loop);
  GET_BLOCK(body);
  GET_BLOCK(ret);
  GET_BLOCK(exit);
#undef GET_BLOCK
  HInstruction* n = MakeParam(DataType::Type::kInt32);
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c1 = graph_->GetIntConstant(1);

  // entry
  HInstruction* cls = MakeClassLoad();
  HInstruction* array = new (GetAllocator()) HNewArray(
      cls, n, /*dex_pc=*/ 0u, DataType::SizeShift(DataType::Type::kInt32));
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(array);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  ManuallyBuildEnvFor(array, {});

  HInstruction* preheader_goto = new (GetAllocator()) HGoto();
  preheader->AddInstruction(preheader_goto);

  // loop
  HPhi* i_phi = new (GetAllocator()) HPhi(GetAllocator(), 0, 0, DataType::Type::kInt32);
  HInstruction* loop_suspend_check = new (GetAllocator()) HSuspendCheck();
  HInstruction* loop_cond = new (GetAllocator()) HLessThan(i_phi, n);
  HIf* loop_if = new (GetAllocator()) HIf(loop_cond);
  loop->AddPhi(i_phi);
  loop->AddInstruction(loop_suspend_check);
  loop->AddInstruction(loop_cond);
  loop->AddInstruction(loop_if);
  CHECK(loop_if->IfTrueSuccessor() == body);
  ManuallyBuildEnvFor(loop_suspend_check, {});

  // body
  HInstruction* body_set =
      new (GetAllocator()) HArraySet(array, i_phi, i_phi, DataType::Type::kInt32, /*dex_pc=*/ 0u);
  HInstruction* body_add = new (GetAllocator()) HAdd(DataType::Type::kInt32, i_phi, c1);
  HInstruction* body_goto = new (GetAllocator()) HGoto();
  body->AddInstruction(body_set);
  body->AddInstruction(body_add);
  body->AddInstruction(body_goto);

  // i_phi inputs
  i_phi->AddInput(c0);
  i_phi->AddInput(body_add);

  // ret
  HInstruction* ret_sub = new (GetAllocator()) HSub(DataType::Type::kInt32, i_phi, c1);
  HInstruction* ret_get1 =
      new (GetAllocator()) HArrayGet(array, ret_sub, DataType::Type::kInt32, /*dex_pc=*/ 0);
  HInstruction* ret_get2 =
      new (GetAllocator()) HArrayGet(array, i_phi, DataType::Type::kInt32, /*dex_pc=*/ 0);
  HInstruction* ret_add = new (GetAllocator()) HAdd(DataType::Type::kInt32, ret_get1, ret_get2);
  HInstruction* ret_return = new (GetAllocator()) HReturn(ret_add);
  ret->AddInstruction(ret_sub);
  ret->AddInstruction(ret_get1);
  ret->AddInstruction(ret_get2);
  ret->AddInstruction(ret_add);
  ret->AddInstruction(ret_return);

  // exit
  SetupExit(exit);

  graph_->ClearDominanceInformation();
  graph_->ClearLoopInformation();
  PerformLSE();

  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_RETAINED(array);
  EXPECT_INS_RETAINED(body_set);
  EXPECT_INS_RETAINED(ret_get1);
  EXPECT_INS_RETAINED(ret_get2);
}

// // ENTRY
// obj = new Obj();
// // ALL should be kept
// switch (parameter_value) {
//   case 1:
//     // Case1
//     obj.field = 1;
//     call_func(obj);
//     break;
//   case 2:
//     // Case2
//     obj.field = 2;
//     call_func(obj);
//     // We don't know what obj.field is now we aren't able to eliminate the read below!
//     break;
//   default:
//     // Case3
//     // TODO This only happens because of limitations on our LSE which is unable
//     //      to materialize co-dependent loop and non-loop phis.
//     // Ideally we'd want to generate
//     // P1 = PHI[3, loop_val]
//     // while (test()) {
//     //   if (test2()) { goto; } else { goto; }
//     //   loop_val = [P1, 5]
//     // }
//     // Currently we aren't able to unfortunately.
//     obj.field = 3;
//     while (test()) {
//       if (test2()) { } else { obj.field = 5; }
//     }
//     break;
// }
// EXIT
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialUnknownMerge) {
  CreateGraph();
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "bswitch"},
                                                  {"bswitch", "case1"},
                                                  {"bswitch", "case2"},
                                                  {"bswitch", "case3"},
                                                  {"case1", "breturn"},
                                                  {"case2", "breturn"},
                                                  {"case3", "loop_pre_header"},
                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_end"},
                                                  {"loop_if_right", "loop_end"},
                                                  {"loop_end", "loop_header"},
                                                  {"loop_header", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(bswitch);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(case1);
  GET_BLOCK(case2);
  GET_BLOCK(case3);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
  GET_BLOCK(loop_end);
#undef GET_BLOCK
  HInstruction* switch_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, switch_val);
  bswitch->AddInstruction(switch_inst);

  HInstruction* write_c1 = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_c1 = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_c1 = new (GetAllocator()) HGoto();
  case1->AddInstruction(write_c1);
  case1->AddInstruction(call_c1);
  case1->AddInstruction(goto_c1);
  call_c1->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_c2 = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_c2 = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_c2 = new (GetAllocator()) HGoto();
  case2->AddInstruction(write_c2);
  case2->AddInstruction(call_c2);
  case2->AddInstruction(goto_c2);
  call_c2->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_c3 = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_c3 = new (GetAllocator()) HGoto();
  case3->AddInstruction(write_c3);
  case3->AddInstruction(goto_c3);

  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_loop_header = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_header = new (GetAllocator()) HIf(call_loop_header);
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(call_loop_header);
  loop_header->AddInstruction(if_loop_header);
  call_loop_header->CopyEnvironmentFrom(cls->GetEnvironment());
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(goto_loop_left);

  HInstruction* write_loop_right = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);

  HInstruction* goto_loop_end = new (GetAllocator()) HGoto();
  loop_end->AddInstruction(goto_loop_end);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSENoPartial();

  EXPECT_INS_RETAINED(read_bottom);
  EXPECT_INS_RETAINED(write_c1);
  EXPECT_INS_RETAINED(write_c2);
  EXPECT_INS_RETAINED(write_c3);
  EXPECT_INS_RETAINED(write_loop_right);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   obj.field = 1;
//   call_func(obj);
//   foo_r = obj.field
// } else {
//   // TO BE ELIMINATED
//   obj.field = 2;
//   // RIGHT
//   // TO BE ELIMINATED
//   foo_l = obj.field;
// }
// EXIT
// return PHI(foo_l, foo_r)
TEST_F(LoadStoreEliminationTest, PartialLoadElimination) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit_REAL",
                                                 { { "entry", "left" },
                                                   { "entry", "right" },
                                                   { "left", "exit" },
                                                   { "right", "exit" },
                                                   { "exit", "exit_REAL" } }));
  HBasicBlock* entry = blks.Get("entry");
  HBasicBlock* left = blks.Get("left");
  HBasicBlock* right = blks.Get("right");
  HBasicBlock* exit = blks.Get("exit");
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* read_left = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(16));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(write_left);
  left->AddInstruction(call_left);
  left->AddInstruction(read_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(16));
  HInstruction* read_right = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(16));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(read_right);
  right->AddInstruction(goto_right);

  HInstruction* phi_final = MakePhi({read_left, read_right});
  HInstruction* return_exit = new (GetAllocator()) HReturn(phi_final);
  exit->AddPhi(phi_final->AsPhi());
  exit->AddInstruction(return_exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSE();

  ASSERT_TRUE(IsRemoved(read_right));
  ASSERT_FALSE(IsRemoved(read_left));
  ASSERT_FALSE(IsRemoved(phi_final));
  ASSERT_TRUE(phi_final->GetInputs()[1] == c2);
  ASSERT_TRUE(phi_final->GetInputs()[0] == read_left);
  ASSERT_TRUE(IsRemoved(write_right));
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   obj.field = 1;
//   call_func(obj);
//   // We don't know what obj.field is now we aren't able to eliminate the read below!
// } else {
//   // DO NOT ELIMINATE
//   obj.field = 2;
//   // RIGHT
// }
// EXIT
// return obj.field
// This test runs with partial LSE disabled.
TEST_F(LoadStoreEliminationTest, PartialLoadPreserved) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit_REAL",
                                                 { { "entry", "left" },
                                                   { "entry", "right" },
                                                   { "left", "exit" },
                                                   { "right", "exit" },
                                                   { "exit", "exit_REAL" } }));
  HBasicBlock* entry = blks.Get("entry");
  HBasicBlock* left = blks.Get("left");
  HBasicBlock* right = blks.Get("right");
  HBasicBlock* exit = blks.Get("exit");
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(write_left);
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  exit->AddInstruction(read_bottom);
  exit->AddInstruction(return_exit);
  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSENoPartial();

  EXPECT_INS_RETAINED(read_bottom) << *read_bottom;
  EXPECT_INS_RETAINED(write_right) << *write_right;
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   obj.field = 1;
//   call_func(obj);
//   // We don't know what obj.field is now we aren't able to eliminate the read below!
// } else {
//   // DO NOT ELIMINATE
//   if (param2) {
//     obj.field = 2;
//   } else {
//     obj.field = 3;
//   }
//   // RIGHT
// }
// EXIT
// return obj.field
// NB This test is for non-partial LSE flow. Normally the obj.field writes will be removed
TEST_F(LoadStoreEliminationTest, PartialLoadPreserved2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit_REAL",
                                                 { { "entry", "left" },
                                                   { "entry", "right_start" },
                                                   { "left", "exit" },
                                                   { "right_start", "right_first" },
                                                   { "right_start", "right_second" },
                                                   { "right_first", "right_end" },
                                                   { "right_second", "right_end" },
                                                   { "right_end", "exit" },
                                                   { "exit", "exit_REAL" } }));
  HBasicBlock* entry = blks.Get("entry");
  HBasicBlock* left = blks.Get("left");
  HBasicBlock* right_start = blks.Get("right_start");
  HBasicBlock* right_first = blks.Get("right_first");
  HBasicBlock* right_second = blks.Get("right_second");
  HBasicBlock* right_end = blks.Get("right_end");
  HBasicBlock* exit = blks.Get("exit");
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* bool_value_2 = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(write_left);
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* right_if = new (GetAllocator()) HIf(bool_value_2);
  right_start->AddInstruction(right_if);

  HInstruction* write_right_first = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right_first = new (GetAllocator()) HGoto();
  right_first->AddInstruction(write_right_first);
  right_first->AddInstruction(goto_right_first);

  HInstruction* write_right_second = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_right_second = new (GetAllocator()) HGoto();
  right_second->AddInstruction(write_right_second);
  right_second->AddInstruction(goto_right_second);

  HInstruction* goto_right_end = new (GetAllocator()) HGoto();
  right_end->AddInstruction(goto_right_end);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  exit->AddInstruction(read_bottom);
  exit->AddInstruction(return_exit);
  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSENoPartial();

  EXPECT_INS_RETAINED(read_bottom);
  EXPECT_INS_RETAINED(write_right_first);
  EXPECT_INS_RETAINED(write_right_second);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
//   obj.field = 1;
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoadElimination2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(write_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
}

template<typename Iter, typename Func>
typename Iter::value_type FindOrNull(Iter begin, Iter end, Func func) {
  static_assert(std::is_pointer_v<typename Iter::value_type>);
  auto it = std::find_if(begin, end, func);
  if (it == end) {
    return nullptr;
  } else {
    return *it;
  }
}

// // ENTRY
// Obj new_inst = new Obj();
// new_inst.foo = 12;
// Obj obj;
// Obj out;
// int first;
// if (param0) {
//   // ESCAPE_ROUTE
//   if (param1) {
//     // LEFT_START
//     if (param2) {
//       // LEFT_LEFT
//       obj = new_inst;
//     } else {
//       // LEFT_RIGHT
//       obj = obj_param;
//     }
//     // LEFT_MERGE
//     // technically the phi is enough to cause an escape but might as well be
//     // thorough.
//     // obj = phi[new_inst, param]
//     escape(obj);
//     out = obj;
//   } else {
//     // RIGHT
//     out = obj_param;
//   }
//   // EXIT
//   // Can't do anything with this since we don't have good tracking for the heap-locations
//   // out = phi[param, phi[new_inst, param]]
//   first = out.foo
// } else {
//   new_inst.foo = 15;
//   first = 13;
// }
// // first = phi[out.foo, 13]
// return first + new_inst.foo;
TEST_F(LoadStoreEliminationTest, PartialPhiPropagation) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "escape_route"},
                                                  {"entry", "noescape_route"},
                                                  {"escape_route", "left"},
                                                  {"escape_route", "right"},
                                                  {"left", "left_left"},
                                                  {"left", "left_right"},
                                                  {"left_left", "left_merge"},
                                                  {"left_right", "left_merge"},
                                                  {"left_merge", "escape_end"},
                                                  {"right", "escape_end"},
                                                  {"escape_end", "breturn"},
                                                  {"noescape_route", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(left_left);
  GET_BLOCK(left_right);
  GET_BLOCK(left_merge);
  GET_BLOCK(escape_end);
  GET_BLOCK(escape_route);
  GET_BLOCK(noescape_route);
#undef GET_BLOCK
  EnsurePredecessorOrder(escape_end, {left_merge, right});
  EnsurePredecessorOrder(left_merge, {left_left, left_right});
  EnsurePredecessorOrder(breturn, {escape_end, noescape_route});
  HInstruction* param0 = MakeParam(DataType::Type::kBool);
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);
  HInstruction* obj_param = MakeParam(DataType::Type::kReference);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c13 = graph_->GetIntConstant(13);
  HInstruction* c15 = graph_->GetIntConstant(15);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* store = MakeIFieldSet(new_inst, c12, MemberOffset(32));
  HInstruction* if_param0 = new (GetAllocator()) HIf(param0);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(store);
  entry->AddInstruction(if_param0);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* store_noescape = MakeIFieldSet(new_inst, c15, MemberOffset(32));
  noescape_route->AddInstruction(store_noescape);
  noescape_route->AddInstruction(new (GetAllocator()) HGoto());

  escape_route->AddInstruction(new (GetAllocator()) HIf(param1));

  HInstruction* if_left = new (GetAllocator()) HIf(param2);
  left->AddInstruction(if_left);

  HInstruction* goto_left_left = new (GetAllocator()) HGoto();
  left_left->AddInstruction(goto_left_left);

  HInstruction* goto_left_right = new (GetAllocator()) HGoto();
  left_right->AddInstruction(goto_left_right);

  HPhi* left_phi = MakePhi({obj_param, new_inst});
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { left_phi });
  HInstruction* goto_left_merge = new (GetAllocator()) HGoto();
  left_merge->AddPhi(left_phi);
  left_merge->AddInstruction(call_left);
  left_merge->AddInstruction(goto_left_merge);
  left_phi->SetCanBeNull(true);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(goto_right);

  HPhi* escape_end_phi = MakePhi({left_phi, obj_param});
  HInstruction* read_escape_end =
      MakeIFieldGet(escape_end_phi, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* goto_escape_end = new (GetAllocator()) HGoto();
  escape_end->AddPhi(escape_end_phi);
  escape_end->AddInstruction(read_escape_end);
  escape_end->AddInstruction(goto_escape_end);

  HPhi* return_phi = MakePhi({read_escape_end, c13});
  HInstruction* read_exit = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_exit = new (GetAllocator()) HAdd(DataType::Type::kInt32, return_phi, read_exit);
  HInstruction* return_exit = new (GetAllocator()) HReturn(add_exit);
  breturn->AddPhi(return_phi);
  breturn->AddInstruction(read_exit);
  breturn->AddInstruction(add_exit);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_);
  std::vector<HPhi*> all_return_phis;
  std::tie(all_return_phis) = FindAllInstructions<HPhi>(graph_, breturn);
  EXPECT_EQ(all_return_phis.size(), 3u);
  EXPECT_INS_RETAINED(return_phi);
  EXPECT_TRUE(std::find(all_return_phis.begin(), all_return_phis.end(), return_phi) !=
              all_return_phis.end());
  HPhi* instance_phi =
      FindOrNull(all_return_phis.begin(), all_return_phis.end(), [&](HPhi* phi) {
        return phi != return_phi && phi->GetType() == DataType::Type::kReference;
      });
  ASSERT_NE(instance_phi, nullptr);
  HPhi* value_phi = FindOrNull(all_return_phis.begin(), all_return_phis.end(), [&](HPhi* phi) {
    return phi != return_phi && phi->GetType() == DataType::Type::kInt32;
  });
  ASSERT_NE(value_phi, nullptr);
  EXPECT_INS_EQ(
      instance_phi->InputAt(0),
      FindSingleInstruction<HNewInstance>(graph_, escape_route->GetSinglePredecessor()));
  // Check materialize block
  EXPECT_INS_EQ(FindSingleInstruction<HInstanceFieldSet>(
                    graph_, escape_route->GetSinglePredecessor())
                    ->InputAt(1),
                c12);

  EXPECT_INS_EQ(instance_phi->InputAt(1), graph_->GetNullConstant());
  EXPECT_INS_EQ(value_phi->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(value_phi->InputAt(1), c15);
  EXPECT_INS_REMOVED(store_noescape);
  EXPECT_INS_EQ(pred_get->GetTarget(), instance_phi);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), value_phi);
}

// // ENTRY
// // To be moved
// // NB Order important. By having alloc and store of obj1 before obj2 that
// // ensure we'll build the materialization for obj1 first (just due to how
// // we iterate.)
// obj1 = new Obj();
// obj2 = new Obj(); // has env[obj1]
// // Swap the order of these
// obj1.foo = param_obj1;
// obj2.foo = param_obj2;
// if (param1) {
//   // LEFT
//   obj2.foo = obj1;
//   if (param2) {
//     // LEFT_LEFT
//     escape(obj2);
//   } else {}
// } else {}
// return select(param3, obj1.foo, obj2.foo);
// EXIT
TEST_P(OrderDependentTestGroup, PredicatedUse) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "left_left"},
                                                  {"left", "left_right"},
                                                  {"left_left", "left_end"},
                                                  {"left_right", "left_end"},
                                                  {"left_end", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(right);
  GET_BLOCK(left);
  GET_BLOCK(left_left);
  GET_BLOCK(left_right);
  GET_BLOCK(left_end);
#undef GET_BLOCK
  TestOrder order = GetParam();
  EnsurePredecessorOrder(breturn, {left_end, right});
  EnsurePredecessorOrder(left_end, {left_left, left_right});
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);
  HInstruction* param3 = MakeParam(DataType::Type::kBool);
  HInstruction* param_obj1 = MakeParam(DataType::Type::kReference);
  HInstruction* param_obj2 = MakeParam(DataType::Type::kReference);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* store1 = MakeIFieldSet(new_inst1, param_obj1, MemberOffset(32));
  HInstruction* store2 = MakeIFieldSet(new_inst2, param_obj2, MemberOffset(32));
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* if_inst = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(cls1);
  entry->AddInstruction(cls2);
  entry->AddInstruction(new_inst1);
  entry->AddInstruction(new_inst2);
  if (order == TestOrder::kSameAsAlloc) {
    entry->AddInstruction(store1);
    entry->AddInstruction(store2);
  } else {
    entry->AddInstruction(store2);
    entry->AddInstruction(store1);
  }
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls1, {});
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst2->CopyEnvironmentFrom(cls1->GetEnvironment());

  // This is the escape of new_inst1
  HInstruction* store_left = MakeIFieldSet(new_inst2, new_inst1, MemberOffset(32));
  HInstruction* if_left = new (GetAllocator()) HIf(param2);
  left->AddInstruction(store_left);
  left->AddInstruction(if_left);

  HInstruction* call_left_left = MakeInvoke(DataType::Type::kVoid, { new_inst2 });
  HInstruction* goto_left_left = new (GetAllocator()) HGoto();
  left_left->AddInstruction(call_left_left);
  left_left->AddInstruction(goto_left_left);
  call_left_left->CopyEnvironmentFrom(new_inst2->GetEnvironment());

  left_right->AddInstruction(new (GetAllocator()) HGoto());
  left_end->AddInstruction(new (GetAllocator()) HGoto());

  right->AddInstruction(new (GetAllocator()) HGoto());

  // Used to distinguish the pred-gets without having to dig through the
  // multiple phi layers.
  constexpr uint32_t kRead1DexPc = 10;
  constexpr uint32_t kRead2DexPc = 20;
  HInstruction* read1 =
      MakeIFieldGet(new_inst1, DataType::Type::kReference, MemberOffset(32), kRead1DexPc);
  read1->SetReferenceTypeInfo(
      ReferenceTypeInfo::CreateUnchecked(graph_->GetHandleCache()->GetObjectClassHandle(), false));
  HInstruction* read2 =
      MakeIFieldGet(new_inst2, DataType::Type::kReference, MemberOffset(32), kRead2DexPc);
  read2->SetReferenceTypeInfo(
      ReferenceTypeInfo::CreateUnchecked(graph_->GetHandleCache()->GetObjectClassHandle(), false));
  HInstruction* sel_return = new (GetAllocator()) HSelect(param3, read1, read2, 0);
  HInstruction* return_exit = new (GetAllocator()) HReturn(sel_return);
  breturn->AddInstruction(read1);
  breturn->AddInstruction(read2);
  breturn->AddInstruction(sel_return);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_RETAINED(call_left_left);
  EXPECT_INS_REMOVED(read1);
  EXPECT_INS_REMOVED(read2);
  EXPECT_INS_REMOVED(new_inst1);
  EXPECT_INS_REMOVED(new_inst2);
  EXPECT_TRUE(new_inst1->GetUses().empty()) << *new_inst1 << " " << new_inst1->GetUses();
  EXPECT_TRUE(new_inst2->GetUses().empty()) << *new_inst2 << " " << new_inst2->GetUses();
  EXPECT_INS_RETAINED(sel_return);
  // Make sure the selector is the same
  EXPECT_INS_EQ(sel_return->InputAt(2), param3);
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::tie(pred_gets) = FindAllInstructions<HPredicatedInstanceFieldGet>(graph_, breturn);
  HPredicatedInstanceFieldGet* pred1 = FindOrNull(pred_gets.begin(), pred_gets.end(), [&](auto i) {
    return i->GetDexPc() == kRead1DexPc;
  });
  HPredicatedInstanceFieldGet* pred2 = FindOrNull(pred_gets.begin(), pred_gets.end(), [&](auto i) {
    return i->GetDexPc() == kRead2DexPc;
  });
  ASSERT_NE(pred1, nullptr);
  ASSERT_NE(pred2, nullptr);
  EXPECT_INS_EQ(sel_return->InputAt(0), pred2);
  EXPECT_INS_EQ(sel_return->InputAt(1), pred1);
  // Check targets
  EXPECT_TRUE(pred1->GetTarget()->IsPhi()) << pred1->DumpWithArgs();
  EXPECT_TRUE(pred2->GetTarget()->IsPhi()) << pred2->DumpWithArgs();
  HInstruction* mat1 = FindSingleInstruction<HNewInstance>(graph_, left->GetSinglePredecessor());
  HInstruction* mat2 =
      FindSingleInstruction<HNewInstance>(graph_, left_left->GetSinglePredecessor());
  EXPECT_INS_EQ(pred1->GetTarget()->InputAt(0), mat1);
  EXPECT_INS_EQ(pred1->GetTarget()->InputAt(1), null_const);
  EXPECT_TRUE(pred2->GetTarget()->InputAt(0)->IsPhi()) << pred2->DumpWithArgs();
  EXPECT_INS_EQ(pred2->GetTarget()->InputAt(0)->InputAt(0), mat2);
  EXPECT_INS_EQ(pred2->GetTarget()->InputAt(0)->InputAt(1), null_const);
  EXPECT_INS_EQ(pred2->GetTarget()->InputAt(1), null_const);
  // Check default values.
  EXPECT_TRUE(pred1->GetDefaultValue()->IsPhi()) << pred1->DumpWithArgs();
  EXPECT_TRUE(pred2->GetDefaultValue()->IsPhi()) << pred2->DumpWithArgs();
  EXPECT_INS_EQ(pred1->GetDefaultValue()->InputAt(0), null_const);
  EXPECT_INS_EQ(pred1->GetDefaultValue()->InputAt(1), param_obj1);
  EXPECT_TRUE(pred2->GetDefaultValue()->InputAt(0)->IsPhi()) << pred2->DumpWithArgs();
  EXPECT_INS_EQ(pred2->GetDefaultValue()->InputAt(0)->InputAt(0), null_const);
  EXPECT_INS_EQ(pred2->GetDefaultValue()->InputAt(0)->InputAt(1), mat1);
  EXPECT_INS_EQ(pred2->GetDefaultValue()->InputAt(1), param_obj2);
}

// // ENTRY
// // To be moved
// // NB Order important. By having alloc and store of obj1 before obj2 that
// // ensure we'll build the materialization for obj1 first (just due to how
// // we iterate.)
// obj1 = new Obj();
// obj.foo = 12;
// obj2 = new Obj(); // has env[obj1]
// obj2.foo = 15;
// if (param1) {
//   // LEFT
//   // Need to update env to nullptr
//   escape(obj1/2);
//   if (param2) {
//     // LEFT_LEFT
//     escape(obj2/1);
//   } else {}
// } else {}
// return obj1.foo + obj2.foo;
// EXIT
TEST_P(OrderDependentTestGroup, PredicatedEnvUse) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "left_left"},
                                                  {"left", "left_right"},
                                                  {"left_left", "left_end"},
                                                  {"left_right", "left_end"},
                                                  {"left_end", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(right);
  GET_BLOCK(left);
  GET_BLOCK(left_left);
  GET_BLOCK(left_right);
  GET_BLOCK(left_end);
#undef GET_BLOCK
  TestOrder order = GetParam();
  EnsurePredecessorOrder(breturn, {left_end, right});
  EnsurePredecessorOrder(left_end, {left_left, left_right});
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c15 = graph_->GetIntConstant(15);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* store1 = MakeIFieldSet(new_inst1, c12, MemberOffset(32));
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* store2 = MakeIFieldSet(new_inst2, c15, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(cls1);
  entry->AddInstruction(cls2);
  entry->AddInstruction(new_inst1);
  entry->AddInstruction(store1);
  entry->AddInstruction(new_inst2);
  entry->AddInstruction(store2);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls1, {});
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());
  ManuallyBuildEnvFor(new_inst2, {new_inst1});

  HInstruction* first_inst = new_inst1;
  HInstruction* second_inst = new_inst2;

  if (order == TestOrder::kReverseOfAlloc) {
    std::swap(first_inst, second_inst);
  }

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { first_inst });
  HInstruction* if_left = new (GetAllocator()) HIf(param2);
  left->AddInstruction(call_left);
  left->AddInstruction(if_left);
  call_left->CopyEnvironmentFrom(new_inst2->GetEnvironment());

  HInstruction* call_left_left = MakeInvoke(DataType::Type::kVoid, { second_inst });
  HInstruction* goto_left_left = new (GetAllocator()) HGoto();
  left_left->AddInstruction(call_left_left);
  left_left->AddInstruction(goto_left_left);
  call_left_left->CopyEnvironmentFrom(new_inst2->GetEnvironment());

  left_right->AddInstruction(new (GetAllocator()) HGoto());
  left_end->AddInstruction(new (GetAllocator()) HGoto());

  right->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* read1 = MakeIFieldGet(new_inst1, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* read2 = MakeIFieldGet(new_inst2, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_return = new (GetAllocator()) HAdd(DataType::Type::kInt32, read1, read2);
  HInstruction* return_exit = new (GetAllocator()) HReturn(add_return);
  breturn->AddInstruction(read1);
  breturn->AddInstruction(read2);
  breturn->AddInstruction(add_return);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HNewInstance* moved_new_inst1;
  HInstanceFieldSet* moved_set1;
  HNewInstance* moved_new_inst2;
  HInstanceFieldSet* moved_set2;
  HBasicBlock* first_mat_block = left->GetSinglePredecessor();
  HBasicBlock* second_mat_block = left_left->GetSinglePredecessor();
  if (order == TestOrder::kReverseOfAlloc) {
    std::swap(first_mat_block, second_mat_block);
  }
  std::tie(moved_new_inst1, moved_set1) =
      FindSingleInstructions<HNewInstance, HInstanceFieldSet>(graph_, first_mat_block);
  std::tie(moved_new_inst2, moved_set2) =
      FindSingleInstructions<HNewInstance, HInstanceFieldSet>(graph_, second_mat_block);
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::vector<HPhi*> phis;
  std::tie(pred_gets, phis) = FindAllInstructions<HPredicatedInstanceFieldGet, HPhi>(graph_);
  EXPECT_NE(moved_new_inst1, nullptr);
  EXPECT_NE(moved_new_inst2, nullptr);
  EXPECT_NE(moved_set1, nullptr);
  EXPECT_NE(moved_set2, nullptr);
  EXPECT_INS_EQ(moved_set1->InputAt(1), c12);
  EXPECT_INS_EQ(moved_set2->InputAt(1), c15);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_left_left);
  EXPECT_INS_REMOVED(store1);
  EXPECT_INS_REMOVED(store2);
  EXPECT_INS_REMOVED(read1);
  EXPECT_INS_REMOVED(read2);
  EXPECT_INS_EQ(moved_new_inst2->GetEnvironment()->GetInstructionAt(0),
                order == TestOrder::kSameAsAlloc
                    ? moved_new_inst1
                    : static_cast<HInstruction*>(graph_->GetNullConstant()));
}

// // ENTRY
// obj1 = new Obj1();
// obj2 = new Obj2();
// val1 = 3;
// val2 = 13;
// // The exact order the stores are written affects what the order we perform
// // partial LSE on the values
// obj1/2.field = val1/2;
// obj2/1.field = val2/1;
// if (parameter_value) {
//   // LEFT
//   escape(obj1);
//   escape(obj2);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj1.field = 2;
//   obj2.field = 12;
// }
// EXIT
// predicated-ELIMINATE
// return obj1.field + obj2.field
TEST_P(OrderDependentTestGroup, FieldSetOrderEnv) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  TestOrder order = GetParam();
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c13 = graph_->GetIntConstant(13);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* write_entry1 = MakeIFieldSet(new_inst1, c3, MemberOffset(32));
  HInstruction* write_entry2 = MakeIFieldSet(new_inst2, c13, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls1);
  entry->AddInstruction(cls2);
  entry->AddInstruction(new_inst1);
  entry->AddInstruction(new_inst2);
  if (order == TestOrder::kSameAsAlloc) {
    entry->AddInstruction(write_entry1);
    entry->AddInstruction(write_entry2);
  } else {
    entry->AddInstruction(write_entry2);
    entry->AddInstruction(write_entry1);
  }
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls1, {});
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());
  ManuallyBuildEnvFor(new_inst2, {new_inst1});

  HInstruction* call_left1 = MakeInvoke(DataType::Type::kVoid, { new_inst1 });
  HInstruction* call_left2 = MakeInvoke(DataType::Type::kVoid, { new_inst2 });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left1);
  left->AddInstruction(call_left2);
  left->AddInstruction(goto_left);
  call_left1->CopyEnvironmentFrom(cls1->GetEnvironment());
  call_left2->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* write_right1 = MakeIFieldSet(new_inst1, c2, MemberOffset(32));
  HInstruction* write_right2 = MakeIFieldSet(new_inst2, c12, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right1);
  right->AddInstruction(write_right2);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom1 = MakeIFieldGet(new_inst1, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* read_bottom2 = MakeIFieldGet(new_inst2, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* combine =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, read_bottom1, read_bottom2);
  HInstruction* return_exit = new (GetAllocator()) HReturn(combine);
  breturn->AddInstruction(read_bottom1);
  breturn->AddInstruction(read_bottom2);
  breturn->AddInstruction(combine);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(write_entry1);
  EXPECT_INS_REMOVED(write_entry2);
  EXPECT_INS_REMOVED(read_bottom1);
  EXPECT_INS_REMOVED(read_bottom2);
  EXPECT_INS_REMOVED(write_right1);
  EXPECT_INS_REMOVED(write_right2);
  EXPECT_INS_RETAINED(call_left1);
  EXPECT_INS_RETAINED(call_left2);
  std::vector<HPhi*> merges;
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::vector<HNewInstance*> materializations;
  std::tie(merges, pred_gets) =
      FindAllInstructions<HPhi, HPredicatedInstanceFieldGet>(graph_, breturn);
  std::tie(materializations) = FindAllInstructions<HNewInstance>(graph_);
  ASSERT_EQ(merges.size(), 4u);
  ASSERT_EQ(pred_gets.size(), 2u);
  ASSERT_EQ(materializations.size(), 2u);
  HPhi* merge_value_return1 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->InputAt(1) == c2;
  });
  HPhi* merge_value_return2 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->InputAt(1) == c12;
  });
  HNewInstance* mat_alloc1 = FindOrNull(materializations.begin(),
                                        materializations.end(),
                                        [&](HNewInstance* n) { return n->InputAt(0) == cls1; });
  HNewInstance* mat_alloc2 = FindOrNull(materializations.begin(),
                                        materializations.end(),
                                        [&](HNewInstance* n) { return n->InputAt(0) == cls2; });
  ASSERT_NE(mat_alloc1, nullptr);
  ASSERT_NE(mat_alloc2, nullptr);
  HPhi* merge_alloc1 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kReference && p->InputAt(0) == mat_alloc1;
  });
  HPhi* merge_alloc2 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kReference && p->InputAt(0) == mat_alloc2;
  });
  ASSERT_NE(merge_alloc1, nullptr);
  HPredicatedInstanceFieldGet* pred_get1 =
      FindOrNull(pred_gets.begin(), pred_gets.end(), [&](HPredicatedInstanceFieldGet* pg) {
        return pg->GetTarget() == merge_alloc1;
      });
  ASSERT_NE(merge_alloc2, nullptr);
  HPredicatedInstanceFieldGet* pred_get2 =
      FindOrNull(pred_gets.begin(), pred_gets.end(), [&](HPredicatedInstanceFieldGet* pg) {
        return pg->GetTarget() == merge_alloc2;
      });
  ASSERT_NE(merge_value_return1, nullptr);
  ASSERT_NE(merge_value_return2, nullptr);
  EXPECT_INS_EQ(merge_alloc1->InputAt(1), graph_->GetNullConstant());
  EXPECT_INS_EQ(merge_alloc2->InputAt(1), graph_->GetNullConstant());
  ASSERT_NE(pred_get1, nullptr);
  EXPECT_INS_EQ(pred_get1->GetTarget(), merge_alloc1);
  EXPECT_INS_EQ(pred_get1->GetDefaultValue(), merge_value_return1)
      << " pred-get is: " << *pred_get1;
  EXPECT_INS_EQ(merge_value_return1->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(merge_value_return1->InputAt(1), c2) << " merge val is: " << *merge_value_return1;
  ASSERT_NE(pred_get2, nullptr);
  EXPECT_INS_EQ(pred_get2->GetTarget(), merge_alloc2);
  EXPECT_INS_EQ(pred_get2->GetDefaultValue(), merge_value_return2)
      << " pred-get is: " << *pred_get2;
  EXPECT_INS_EQ(merge_value_return2->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(merge_value_return2->InputAt(1), c12) << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(mat_alloc2->GetEnvironment()->GetInstructionAt(0), mat_alloc1);
}

// // TODO We can compile this better if we are better able to understand lifetimes.
// // ENTRY
// obj1 = new Obj1();
// obj2 = new Obj2();
// // The exact order the stores are written affects what the order we perform
// // partial LSE on the values
// obj{1,2}.var = param_obj;
// obj{2,1}.var = param_obj;
// if (param_1) {
//   // EARLY_RETURN
//   return;
// }
// // escape of obj1
// obj2.var = obj1;
// if (param_2) {
//   // escape of obj2 with a materialization that uses obj1
//   escape(obj2);
// }
// // EXIT
// return;
TEST_P(OrderDependentTestGroup, MaterializationMovedUse) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "early_return"},
                                                  {"early_return", "exit"},
                                                  {"entry", "escape_1"},
                                                  {"escape_1", "escape_2"},
                                                  {"escape_1", "escape_1_crit_break"},
                                                  {"escape_1_crit_break", "exit"},
                                                  {"escape_2", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(early_return);
  GET_BLOCK(escape_1);
  GET_BLOCK(escape_1_crit_break);
  GET_BLOCK(escape_2);
#undef GET_BLOCK
  TestOrder order = GetParam();
  HInstruction* param_1 = MakeParam(DataType::Type::kBool);
  HInstruction* param_2 = MakeParam(DataType::Type::kBool);
  HInstruction* param_obj = MakeParam(DataType::Type::kReference);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* write_entry1 = MakeIFieldSet(new_inst1, param_obj, MemberOffset(32));
  HInstruction* write_entry2 = MakeIFieldSet(new_inst2, param_obj, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(param_1);
  entry->AddInstruction(cls1);
  entry->AddInstruction(cls2);
  entry->AddInstruction(new_inst1);
  entry->AddInstruction(new_inst2);
  if (order == TestOrder::kSameAsAlloc) {
    entry->AddInstruction(write_entry1);
    entry->AddInstruction(write_entry2);
  } else {
    entry->AddInstruction(write_entry2);
    entry->AddInstruction(write_entry1);
  }
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls1, {});
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst2->CopyEnvironmentFrom(cls1->GetEnvironment());

  early_return->AddInstruction(new (GetAllocator()) HReturnVoid());

  HInstruction* escape_1_set = MakeIFieldSet(new_inst2, new_inst1, MemberOffset(32));
  HInstruction* escape_1_if = new (GetAllocator()) HIf(param_2);
  escape_1->AddInstruction(escape_1_set);
  escape_1->AddInstruction(escape_1_if);

  escape_1_crit_break->AddInstruction(new (GetAllocator()) HReturnVoid());

  HInstruction* escape_2_call = MakeInvoke(DataType::Type::kVoid, {new_inst2});
  HInstruction* escape_2_return = new (GetAllocator()) HReturnVoid();
  escape_2->AddInstruction(escape_2_call);
  escape_2->AddInstruction(escape_2_return);
  escape_2_call->CopyEnvironmentFrom(cls1->GetEnvironment());

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(new_inst1);
  EXPECT_INS_REMOVED(new_inst2);
  EXPECT_INS_REMOVED(write_entry1);
  EXPECT_INS_REMOVED(write_entry2);
  EXPECT_INS_REMOVED(escape_1_set);
  EXPECT_INS_RETAINED(escape_2_call);

  HInstruction* obj1_mat =
      FindSingleInstruction<HNewInstance>(graph_, escape_1->GetSinglePredecessor());
  HInstruction* obj1_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, escape_1->GetSinglePredecessor());
  HInstruction* obj2_mat =
      FindSingleInstruction<HNewInstance>(graph_, escape_2->GetSinglePredecessor());
  HInstruction* obj2_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, escape_2->GetSinglePredecessor());
  ASSERT_TRUE(obj1_mat != nullptr);
  ASSERT_TRUE(obj2_mat != nullptr);
  ASSERT_TRUE(obj1_set != nullptr);
  ASSERT_TRUE(obj2_set != nullptr);
  EXPECT_INS_EQ(obj1_set->InputAt(0), obj1_mat);
  EXPECT_INS_EQ(obj1_set->InputAt(1), param_obj);
  EXPECT_INS_EQ(obj2_set->InputAt(0), obj2_mat);
  EXPECT_INS_EQ(obj2_set->InputAt(1), obj1_mat);
}

INSTANTIATE_TEST_SUITE_P(LoadStoreEliminationTest,
                         OrderDependentTestGroup,
                         testing::Values(TestOrder::kSameAsAlloc, TestOrder::kReverseOfAlloc));

// // ENTRY
// // To be moved
// obj = new Obj();
// obj.foo = 12;
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// } else {}
// EXIT
TEST_F(LoadStoreEliminationTest, MovePredicatedAlloc) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"right", "breturn"},
                                                  {"left", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c12 = graph_->GetIntConstant(12);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* store = MakeIFieldSet(new_inst, c12, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(store);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  right->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* return_exit = new (GetAllocator()) HReturnVoid();
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HNewInstance* moved_new_inst = nullptr;
  HInstanceFieldSet* moved_set = nullptr;
  std::tie(moved_new_inst, moved_set) =
      FindSingleInstructions<HNewInstance, HInstanceFieldSet>(graph_);
  EXPECT_NE(moved_new_inst, nullptr);
  EXPECT_NE(moved_set, nullptr);
  EXPECT_INS_RETAINED(call_left);
  // store removed or moved.
  EXPECT_NE(store->GetBlock(), entry);
  // New-inst removed or moved.
  EXPECT_NE(new_inst->GetBlock(), entry);
  EXPECT_INS_EQ(moved_set->InputAt(0), moved_new_inst);
  EXPECT_INS_EQ(moved_set->InputAt(1), c12);
}

// // ENTRY
// // To be moved
// obj = new Obj();
// obj.foo = 12;
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// }
// EXIT
// int a = obj.foo;
// obj.foo = 13;
// noescape();
// int b = obj.foo;
// obj.foo = 14;
// noescape();
// int c = obj.foo;
// obj.foo = 15;
// noescape();
// return a + b + c
TEST_F(LoadStoreEliminationTest, MutiPartialLoadStore) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"right", "breturn"},
                                                  {"left", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c13 = graph_->GetIntConstant(13);
  HInstruction* c14 = graph_->GetIntConstant(14);
  HInstruction* c15 = graph_->GetIntConstant(15);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* store = MakeIFieldSet(new_inst, c12, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(store);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(goto_right);

  HInstruction* a_val = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* a_reset = MakeIFieldSet(new_inst, c13, MemberOffset(32));
  HInstruction* a_noescape = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* b_val = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* b_reset = MakeIFieldSet(new_inst, c14, MemberOffset(32));
  HInstruction* b_noescape = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* c_val = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* c_reset = MakeIFieldSet(new_inst, c15, MemberOffset(32));
  HInstruction* c_noescape = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* add_1_exit = new (GetAllocator()) HAdd(DataType::Type::kInt32, a_val, b_val);
  HInstruction* add_2_exit = new (GetAllocator()) HAdd(DataType::Type::kInt32, c_val, add_1_exit);
  HInstruction* return_exit = new (GetAllocator()) HReturn(add_2_exit);
  breturn->AddInstruction(a_val);
  breturn->AddInstruction(a_reset);
  breturn->AddInstruction(a_noescape);
  breturn->AddInstruction(b_val);
  breturn->AddInstruction(b_reset);
  breturn->AddInstruction(b_noescape);
  breturn->AddInstruction(c_val);
  breturn->AddInstruction(c_reset);
  breturn->AddInstruction(c_noescape);
  breturn->AddInstruction(add_1_exit);
  breturn->AddInstruction(add_2_exit);
  breturn->AddInstruction(return_exit);
  ManuallyBuildEnvFor(a_noescape, {new_inst, a_val});
  ManuallyBuildEnvFor(b_noescape, {new_inst, a_val, b_val});
  ManuallyBuildEnvFor(c_noescape, {new_inst, a_val, b_val, c_val});

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HNewInstance* moved_new_inst = nullptr;
  HInstanceFieldSet* moved_set = nullptr;
  std::tie(moved_new_inst, moved_set) =
      FindSingleInstructions<HNewInstance, HInstanceFieldSet>(graph_, left->GetSinglePredecessor());
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::vector<HInstanceFieldSet*> pred_sets;
  std::vector<HPhi*> return_phis;
  std::tie(return_phis, pred_gets, pred_sets) =
      FindAllInstructions<HPhi, HPredicatedInstanceFieldGet, HInstanceFieldSet>(graph_, breturn);
  ASSERT_EQ(return_phis.size(), 2u);
  HPhi* inst_phi = return_phis[0];
  HPhi* val_phi = return_phis[1];
  if (inst_phi->GetType() != DataType::Type::kReference) {
    std::swap(inst_phi, val_phi);
  }
  ASSERT_NE(moved_new_inst, nullptr);
  EXPECT_INS_EQ(inst_phi->InputAt(0), moved_new_inst);
  EXPECT_INS_EQ(inst_phi->InputAt(1), graph_->GetNullConstant());
  EXPECT_INS_EQ(val_phi->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_EQ(val_phi->InputAt(1), c12);
  ASSERT_EQ(pred_gets.size(), 3u);
  ASSERT_EQ(pred_gets.size(), pred_sets.size());
  std::vector<HInstruction*> set_values{c13, c14, c15};
  std::vector<HInstruction*> get_values{val_phi, c13, c14};
  ASSERT_NE(moved_set, nullptr);
  EXPECT_INS_EQ(moved_set->InputAt(0), moved_new_inst);
  EXPECT_INS_EQ(moved_set->InputAt(1), c12);
  EXPECT_INS_RETAINED(call_left);
  // store removed or moved.
  EXPECT_NE(store->GetBlock(), entry);
  // New-inst removed or moved.
  EXPECT_NE(new_inst->GetBlock(), entry);
  for (auto [get, val] : ZipLeft(MakeIterationRange(pred_gets), MakeIterationRange(get_values))) {
    EXPECT_INS_EQ(get->GetDefaultValue(), val);
  }
  for (auto [set, val] : ZipLeft(MakeIterationRange(pred_sets), MakeIterationRange(set_values))) {
    EXPECT_INS_EQ(set->InputAt(1), val);
    EXPECT_TRUE(set->GetIsPredicatedSet()) << *set;
  }
  EXPECT_INS_RETAINED(a_noescape);
  EXPECT_INS_RETAINED(b_noescape);
  EXPECT_INS_RETAINED(c_noescape);
  EXPECT_INS_EQ(add_1_exit->InputAt(0), pred_gets[0]);
  EXPECT_INS_EQ(add_1_exit->InputAt(1), pred_gets[1]);
  EXPECT_INS_EQ(add_2_exit->InputAt(0), pred_gets[2]);

  EXPECT_EQ(a_noescape->GetEnvironment()->Size(), 2u);
  EXPECT_INS_EQ(a_noescape->GetEnvironment()->GetInstructionAt(0), inst_phi);
  EXPECT_INS_EQ(a_noescape->GetEnvironment()->GetInstructionAt(1), pred_gets[0]);
  EXPECT_EQ(b_noescape->GetEnvironment()->Size(), 3u);
  EXPECT_INS_EQ(b_noescape->GetEnvironment()->GetInstructionAt(0), inst_phi);
  EXPECT_INS_EQ(b_noescape->GetEnvironment()->GetInstructionAt(1), pred_gets[0]);
  EXPECT_INS_EQ(b_noescape->GetEnvironment()->GetInstructionAt(2), pred_gets[1]);
  EXPECT_EQ(c_noescape->GetEnvironment()->Size(), 4u);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(0), inst_phi);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(1), pred_gets[0]);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(2), pred_gets[1]);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(3), pred_gets[2]);
}

// // ENTRY
// // To be moved
// obj = new Obj();
// obj.foo = 12;
// int a = obj.foo;
// obj.foo = 13;
// noescape();
// int b = obj.foo;
// obj.foo = 14;
// noescape();
// int c = obj.foo;
// obj.foo = 15;
// noescape();
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// }
// EXIT
// return a + b + c + obj.foo
TEST_F(LoadStoreEliminationTest, MutiPartialLoadStore2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  // Need to have an actual entry block since we check env-layout and the way we
  // add constants would screw this up otherwise.
  AdjacencyListGraph blks(SetupFromAdjacencyList("start",
                                                 "exit",
                                                 {{"start", "entry"},
                                                  {"entry", "left"},
                                                  {"entry", "right"},
                                                  {"right", "breturn"},
                                                  {"left", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(start);
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c13 = graph_->GetIntConstant(13);
  HInstruction* c14 = graph_->GetIntConstant(14);
  HInstruction* c15 = graph_->GetIntConstant(15);

  HInstruction* start_suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* start_goto = new (GetAllocator()) HGoto();

  start->AddInstruction(start_suspend);
  start->AddInstruction(start_goto);
  ManuallyBuildEnvFor(start_suspend, {});

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* store = MakeIFieldSet(new_inst, c12, MemberOffset(32));

  HInstruction* a_val = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* a_reset = MakeIFieldSet(new_inst, c13, MemberOffset(32));
  HInstruction* a_noescape = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* b_val = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* b_reset = MakeIFieldSet(new_inst, c14, MemberOffset(32));
  HInstruction* b_noescape = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* c_val = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* c_reset = MakeIFieldSet(new_inst, c15, MemberOffset(32));
  HInstruction* c_noescape = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(store);
  entry->AddInstruction(a_val);
  entry->AddInstruction(a_reset);
  entry->AddInstruction(a_noescape);
  entry->AddInstruction(b_val);
  entry->AddInstruction(b_reset);
  entry->AddInstruction(b_noescape);
  entry->AddInstruction(c_val);
  entry->AddInstruction(c_reset);
  entry->AddInstruction(c_noescape);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());
  ManuallyBuildEnvFor(a_noescape, {new_inst, a_val});
  ManuallyBuildEnvFor(b_noescape, {new_inst, a_val, b_val});
  ManuallyBuildEnvFor(c_noescape, {new_inst, a_val, b_val, c_val});

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(c_noescape->GetEnvironment());

  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(goto_right);

  HInstruction* val_exit = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_1_exit = new (GetAllocator()) HAdd(DataType::Type::kInt32, a_val, b_val);
  HInstruction* add_2_exit = new (GetAllocator()) HAdd(DataType::Type::kInt32, c_val, add_1_exit);
  HInstruction* add_3_exit =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, val_exit, add_2_exit);
  HInstruction* return_exit = new (GetAllocator()) HReturn(add_3_exit);
  breturn->AddInstruction(val_exit);
  breturn->AddInstruction(add_1_exit);
  breturn->AddInstruction(add_2_exit);
  breturn->AddInstruction(add_3_exit);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HNewInstance* moved_new_inst = nullptr;
  HInstanceFieldSet* moved_set = nullptr;
  std::tie(moved_new_inst, moved_set) =
      FindSingleInstructions<HNewInstance, HInstanceFieldSet>(graph_, left->GetSinglePredecessor());
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::vector<HInstanceFieldSet*> pred_sets;
  std::vector<HPhi*> return_phis;
  std::tie(return_phis, pred_gets, pred_sets) =
      FindAllInstructions<HPhi, HPredicatedInstanceFieldGet, HInstanceFieldSet>(graph_, breturn);
  ASSERT_EQ(return_phis.size(), 2u);
  HPhi* inst_phi = return_phis[0];
  HPhi* val_phi = return_phis[1];
  if (inst_phi->GetType() != DataType::Type::kReference) {
    std::swap(inst_phi, val_phi);
  }
  ASSERT_NE(moved_new_inst, nullptr);
  EXPECT_INS_EQ(inst_phi->InputAt(0), moved_new_inst);
  EXPECT_INS_EQ(inst_phi->InputAt(1), graph_->GetNullConstant());
  EXPECT_INS_EQ(val_phi->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(val_phi->InputAt(1), c15);
  ASSERT_EQ(pred_gets.size(), 1u);
  ASSERT_EQ(pred_sets.size(), 0u);
  ASSERT_NE(moved_set, nullptr);
  EXPECT_INS_EQ(moved_set->InputAt(0), moved_new_inst);
  EXPECT_INS_EQ(moved_set->InputAt(1), c15);
  EXPECT_INS_RETAINED(call_left);
  // store removed or moved.
  EXPECT_NE(store->GetBlock(), entry);
  // New-inst removed or moved.
  EXPECT_NE(new_inst->GetBlock(), entry);
  EXPECT_INS_REMOVED(a_val);
  EXPECT_INS_REMOVED(b_val);
  EXPECT_INS_REMOVED(c_val);
  EXPECT_INS_RETAINED(a_noescape);
  EXPECT_INS_RETAINED(b_noescape);
  EXPECT_INS_RETAINED(c_noescape);
  EXPECT_INS_EQ(add_1_exit->InputAt(0), c12);
  EXPECT_INS_EQ(add_1_exit->InputAt(1), c13);
  EXPECT_INS_EQ(add_2_exit->InputAt(0), c14);
  EXPECT_INS_EQ(add_2_exit->InputAt(1), add_1_exit);
  EXPECT_INS_EQ(add_3_exit->InputAt(0), pred_gets[0]);
  EXPECT_INS_EQ(pred_gets[0]->GetDefaultValue(), val_phi);
  EXPECT_INS_EQ(add_3_exit->InputAt(1), add_2_exit);
  EXPECT_EQ(a_noescape->GetEnvironment()->Size(), 2u);
  EXPECT_INS_EQ(a_noescape->GetEnvironment()->GetInstructionAt(0), graph_->GetNullConstant());
  EXPECT_INS_EQ(a_noescape->GetEnvironment()->GetInstructionAt(1), c12);
  EXPECT_EQ(b_noescape->GetEnvironment()->Size(), 3u);
  EXPECT_INS_EQ(b_noescape->GetEnvironment()->GetInstructionAt(0), graph_->GetNullConstant());
  EXPECT_INS_EQ(b_noescape->GetEnvironment()->GetInstructionAt(1), c12);
  EXPECT_INS_EQ(b_noescape->GetEnvironment()->GetInstructionAt(2), c13);
  EXPECT_EQ(c_noescape->GetEnvironment()->Size(), 4u);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(0), graph_->GetNullConstant());
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(1), c12);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(2), c13);
  EXPECT_INS_EQ(c_noescape->GetEnvironment()->GetInstructionAt(3), c14);
}

// // ENTRY
// // To be moved
// obj = new Obj();
// // Transforms required for creation non-trivial and unimportant
// if (parameter_value) {
//   obj.foo = 10
// } else {
//   obj.foo = 12;
// }
// if (parameter_value_2) {
//   escape(obj);
// }
// EXIT
TEST_F(LoadStoreEliminationTest, MovePredicatedAlloc2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left_set"},
                                                  {"entry", "right_set"},
                                                  {"left_set", "merge_crit_break"},
                                                  {"right_set", "merge_crit_break"},
                                                  {"merge_crit_break", "merge"},
                                                  {"merge", "escape"},
                                                  {"escape", "breturn"},
                                                  {"merge", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left_set);
  GET_BLOCK(right_set);
  GET_BLOCK(merge);
  GET_BLOCK(merge_crit_break);
  GET_BLOCK(escape);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {merge, escape});
  EnsurePredecessorOrder(merge_crit_break, {left_set, right_set});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* bool_value_2 = MakeParam(DataType::Type::kBool);
  HInstruction* c10 = graph_->GetIntConstant(10);
  HInstruction* c12 = graph_->GetIntConstant(12);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* store_left = MakeIFieldSet(new_inst, c10, MemberOffset(32));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left_set->AddInstruction(store_left);
  left_set->AddInstruction(goto_left);

  HInstruction* store_right = MakeIFieldSet(new_inst, c12, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right_set->AddInstruction(store_right);
  right_set->AddInstruction(goto_right);

  merge_crit_break->AddInstruction(new (GetAllocator()) HGoto());
  HInstruction* if_merge = new (GetAllocator()) HIf(bool_value_2);
  merge->AddInstruction(if_merge);

  HInstruction* escape_instruction = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* escape_goto = new (GetAllocator()) HGoto();
  escape->AddInstruction(escape_instruction);
  escape->AddInstruction(escape_goto);
  escape_instruction->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* return_exit = new (GetAllocator()) HReturnVoid();
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HNewInstance* moved_new_inst;
  HInstanceFieldSet* moved_set;
  std::tie(moved_new_inst, moved_set) =
      FindSingleInstructions<HNewInstance, HInstanceFieldSet>(graph_);
  HPhi* merge_phi = FindSingleInstruction<HPhi>(graph_, merge_crit_break);
  HPhi* alloc_phi = FindSingleInstruction<HPhi>(graph_, breturn);
  EXPECT_INS_EQ(moved_new_inst, moved_set->InputAt(0));
  ASSERT_NE(alloc_phi, nullptr);
  EXPECT_EQ(alloc_phi->InputAt(0), graph_->GetNullConstant())
      << alloc_phi->GetBlock()->GetPredecessors()[0]->GetBlockId() << " " << *alloc_phi;
  EXPECT_TRUE(alloc_phi->InputAt(1)->IsNewInstance()) << *alloc_phi;
  ASSERT_NE(merge_phi, nullptr);
  EXPECT_EQ(merge_phi->InputCount(), 2u);
  EXPECT_INS_EQ(merge_phi->InputAt(0), c10);
  EXPECT_INS_EQ(merge_phi->InputAt(1), c12);
  EXPECT_TRUE(merge_phi->GetUses().HasExactlyOneElement());
  EXPECT_INS_EQ(merge_phi->GetUses().front().GetUser(), moved_set);
  EXPECT_INS_RETAINED(escape_instruction);
  EXPECT_INS_EQ(escape_instruction->InputAt(0), moved_new_inst);
  // store removed or moved.
  EXPECT_NE(store_left->GetBlock(), left_set);
  EXPECT_NE(store_right->GetBlock(), left_set);
  // New-inst removed or moved.
  EXPECT_NE(new_inst->GetBlock(), entry);
}

// // ENTRY
// // To be moved
// obj = new Obj();
// switch(args) {
//   default:
//     return obj.a;
//   case b:
//     obj.a = 5; break;
//   case c:
//     obj.b = 4; break;
// }
// escape(obj);
// return obj.a;
// EXIT
TEST_F(LoadStoreEliminationTest, MovePredicatedAlloc3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "early_return"},
                                                  {"entry", "set_one"},
                                                  {"entry", "set_two"},
                                                  {"early_return", "exit"},
                                                  {"set_one", "escape"},
                                                  {"set_two", "escape"},
                                                  {"escape", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(escape);
  GET_BLOCK(early_return);
  GET_BLOCK(set_one);
  GET_BLOCK(set_two);
#undef GET_BLOCK
  EnsurePredecessorOrder(escape, {set_one, set_two});
  HInstruction* int_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c4 = graph_->GetIntConstant(4);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, int_val);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(switch_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* store_one = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* goto_one = new (GetAllocator()) HGoto();
  set_one->AddInstruction(store_one);
  set_one->AddInstruction(goto_one);

  HInstruction* store_two = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_two = new (GetAllocator()) HGoto();
  set_two->AddInstruction(store_two);
  set_two->AddInstruction(goto_two);

  HInstruction* read_early = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_early = new (GetAllocator()) HReturn(read_early);
  early_return->AddInstruction(read_early);
  early_return->AddInstruction(return_early);

  HInstruction* escape_instruction = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* read_escape = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_escape = new (GetAllocator()) HReturn(read_escape);
  escape->AddInstruction(escape_instruction);
  escape->AddInstruction(read_escape);
  escape->AddInstruction(return_escape);
  escape_instruction->CopyEnvironmentFrom(cls->GetEnvironment());

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  // Each escaping switch path gets its own materialization block.
  // Blocks:
  //   early_return(5) -> [exit(4)]
  //   entry(3) -> [early_return(5), <Unnamed>(9), <Unnamed>(10)]
  //   escape(8) -> [exit(4)]
  //   exit(4) -> []
  //   set_one(6) -> [escape(8)]
  //   set_two(7) -> [escape(8)]
  //   <Unnamed>(10) -> [set_two(7)]
  //   <Unnamed>(9) -> [set_one(6)]
  HBasicBlock* materialize_one = set_one->GetSinglePredecessor();
  HBasicBlock* materialize_two = set_two->GetSinglePredecessor();
  HNewInstance* materialization_ins_one =
      FindSingleInstruction<HNewInstance>(graph_, materialize_one);
  HNewInstance* materialization_ins_two =
      FindSingleInstruction<HNewInstance>(graph_, materialize_two);
  HPhi* new_phi = FindSingleInstruction<HPhi>(graph_, escape);
  EXPECT_NE(materialization_ins_one, nullptr);
  EXPECT_NE(materialization_ins_two, nullptr);
  EXPECT_EQ(materialization_ins_one, new_phi->InputAt(0))
      << *materialization_ins_one << " vs " << *new_phi;
  EXPECT_EQ(materialization_ins_two, new_phi->InputAt(1))
      << *materialization_ins_two << " vs " << *new_phi;

  EXPECT_INS_RETAINED(escape_instruction);
  EXPECT_INS_RETAINED(read_escape);
  EXPECT_EQ(read_escape->InputAt(0), new_phi) << *new_phi << " vs " << *read_escape->InputAt(0);
  EXPECT_EQ(store_one->InputAt(0), materialization_ins_one);
  EXPECT_EQ(store_two->InputAt(0), materialization_ins_two);
  EXPECT_EQ(escape_instruction->InputAt(0), new_phi);
  EXPECT_INS_REMOVED(read_early);
  EXPECT_EQ(return_early->InputAt(0), c0);
}

// // ENTRY
// // To be moved
// obj = new Obj();
// switch(args) {
//   case a:
//     // set_one_and_escape
//     obj.a = 5;
//     escape(obj);
//     // FALLTHROUGH
//   case c:
//     // set_two
//     obj.a = 4; break;
//   default:
//     return obj.a;
// }
// escape(obj);
// return obj.a;
// EXIT
TEST_F(LoadStoreEliminationTest, MovePredicatedAlloc4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  // Break the critical edge between entry and set_two with the
  // set_two_critical_break node. Graph simplification would do this for us if
  // we didn't do it manually. This way we have a nice-name for debugging and
  // testing.
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "early_return"},
                                                  {"entry", "set_one_and_escape"},
                                                  {"entry", "set_two_critical_break"},
                                                  {"set_two_critical_break", "set_two"},
                                                  {"early_return", "exit"},
                                                  {"set_one_and_escape", "set_two"},
                                                  {"set_two", "escape"},
                                                  {"escape", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(escape);
  GET_BLOCK(early_return);
  GET_BLOCK(set_one_and_escape);
  GET_BLOCK(set_two);
  GET_BLOCK(set_two_critical_break);
#undef GET_BLOCK
  EnsurePredecessorOrder(set_two, {set_one_and_escape, set_two_critical_break});
  HInstruction* int_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c4 = graph_->GetIntConstant(4);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, int_val);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(switch_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* store_one = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* escape_one = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_one = new (GetAllocator()) HGoto();
  set_one_and_escape->AddInstruction(store_one);
  set_one_and_escape->AddInstruction(escape_one);
  set_one_and_escape->AddInstruction(goto_one);
  escape_one->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_crit_break = new (GetAllocator()) HGoto();
  set_two_critical_break->AddInstruction(goto_crit_break);

  HInstruction* store_two = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_two = new (GetAllocator()) HGoto();
  set_two->AddInstruction(store_two);
  set_two->AddInstruction(goto_two);

  HInstruction* read_early = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_early = new (GetAllocator()) HReturn(read_early);
  early_return->AddInstruction(read_early);
  early_return->AddInstruction(return_early);

  HInstruction* escape_instruction = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* read_escape = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_escape = new (GetAllocator()) HReturn(read_escape);
  escape->AddInstruction(escape_instruction);
  escape->AddInstruction(read_escape);
  escape->AddInstruction(return_escape);
  escape_instruction->CopyEnvironmentFrom(cls->GetEnvironment());

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_early);
  EXPECT_EQ(return_early->InputAt(0), c0);
  // Each escaping switch path gets its own materialization block.
  // Blocks:
  //   early_return(5) -> [exit(4)]
  //   entry(3) -> [early_return(5), <Unnamed>(10), <Unnamed>(11)]
  //   escape(9) -> [exit(4)]
  //   exit(4) -> []
  //   set_one_and_escape(6) -> [set_two(8)]
  //   set_two(8) -> [escape(9)]
  //   set_two_critical_break(7) -> [set_two(8)]
  //   <Unnamed>(11) -> [set_two_critical_break(7)]
  //   <Unnamed>(10) -> [set_one_and_escape(6)]
  HBasicBlock* materialize_one = set_one_and_escape->GetSinglePredecessor();
  HBasicBlock* materialize_two = set_two_critical_break->GetSinglePredecessor();
  HNewInstance* materialization_ins_one =
      FindSingleInstruction<HNewInstance>(graph_, materialize_one);
  HNewInstance* materialization_ins_two =
      FindSingleInstruction<HNewInstance>(graph_, materialize_two);
  HPhi* new_phi = FindSingleInstruction<HPhi>(graph_, set_two);
  ASSERT_NE(new_phi, nullptr);
  ASSERT_NE(materialization_ins_one, nullptr);
  ASSERT_NE(materialization_ins_two, nullptr);
  EXPECT_INS_EQ(materialization_ins_one, new_phi->InputAt(0));
  EXPECT_INS_EQ(materialization_ins_two, new_phi->InputAt(1));

  EXPECT_INS_EQ(store_one->InputAt(0), materialization_ins_one);
  EXPECT_INS_EQ(store_two->InputAt(0), new_phi) << *store_two << " vs " << *new_phi;
  EXPECT_INS_EQ(escape_instruction->InputAt(0), new_phi);
  EXPECT_INS_RETAINED(escape_one);
  EXPECT_INS_EQ(escape_one->InputAt(0), materialization_ins_one);
  EXPECT_INS_RETAINED(escape_instruction);
  EXPECT_INS_RETAINED(read_escape);
  EXPECT_EQ(read_escape->InputAt(0), new_phi) << *new_phi << " vs " << *read_escape->InputAt(0);
}

// // ENTRY
// // To be moved
// obj = new Obj();
// switch(args) {
//   case a:
//     // set_one
//     obj.a = 5;
//     // nb passthrough
//   case c:
//     // set_two_and_escape
//     obj.a += 4;
//     escape(obj);
//     break;
//   default:
//     obj.a = 10;
// }
// return obj.a;
// EXIT
TEST_F(LoadStoreEliminationTest, MovePredicatedAlloc5) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  // Break the critical edge between entry and set_two with the
  // set_two_critical_break node. Graph simplification would do this for us if
  // we didn't do it manually. This way we have a nice-name for debugging and
  // testing.
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "set_noescape"},
                                                  {"entry", "set_one"},
                                                  {"entry", "set_two_critical_break"},
                                                  {"set_two_critical_break", "set_two_and_escape"},
                                                  {"set_noescape", "breturn"},
                                                  {"set_one", "set_two_and_escape"},
                                                  {"set_two_and_escape", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(set_noescape);
  GET_BLOCK(set_one);
  GET_BLOCK(set_two_and_escape);
  GET_BLOCK(set_two_critical_break);
#undef GET_BLOCK
  EnsurePredecessorOrder(set_two_and_escape, {set_one, set_two_critical_break});
  EnsurePredecessorOrder(breturn, {set_two_and_escape, set_noescape});
  HInstruction* int_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c4 = graph_->GetIntConstant(4);
  HInstruction* c5 = graph_->GetIntConstant(5);
  HInstruction* c10 = graph_->GetIntConstant(10);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, int_val);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(switch_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* store_one = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_one = new (GetAllocator()) HGoto();
  set_one->AddInstruction(store_one);
  set_one->AddInstruction(goto_one);

  HInstruction* goto_crit_break = new (GetAllocator()) HGoto();
  set_two_critical_break->AddInstruction(goto_crit_break);

  HInstruction* get_two = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_two = new (GetAllocator()) HAdd(DataType::Type::kInt32, get_two, c4);
  HInstruction* store_two = MakeIFieldSet(new_inst, add_two, MemberOffset(32));
  HInstruction* escape_two = MakeInvoke(DataType::Type::kVoid, {new_inst});
  HInstruction* goto_two = new (GetAllocator()) HGoto();
  set_two_and_escape->AddInstruction(get_two);
  set_two_and_escape->AddInstruction(add_two);
  set_two_and_escape->AddInstruction(store_two);
  set_two_and_escape->AddInstruction(escape_two);
  set_two_and_escape->AddInstruction(goto_two);
  escape_two->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* store_noescape = MakeIFieldSet(new_inst, c10, MemberOffset(32));
  HInstruction* goto_noescape = new (GetAllocator()) HGoto();
  set_noescape->AddInstruction(store_noescape);
  set_noescape->AddInstruction(goto_noescape);

  HInstruction* read_breturn = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_breturn = new (GetAllocator()) HReturn(read_breturn);
  breturn->AddInstruction(read_breturn);
  breturn->AddInstruction(return_breturn);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  // Normal LSE can get rid of these two.
  EXPECT_INS_REMOVED(store_one);
  EXPECT_INS_REMOVED(get_two);
  EXPECT_INS_RETAINED(add_two);
  EXPECT_TRUE(add_two->InputAt(0)->IsPhi());
  EXPECT_INS_EQ(add_two->InputAt(0)->InputAt(0), c5);
  EXPECT_INS_EQ(add_two->InputAt(0)->InputAt(1), c0);
  EXPECT_INS_EQ(add_two->InputAt(1), c4);

  HBasicBlock* materialize_one = set_one->GetSinglePredecessor();
  HBasicBlock* materialize_two = set_two_critical_break->GetSinglePredecessor();
  HNewInstance* materialization_ins_one =
      FindSingleInstruction<HNewInstance>(graph_, materialize_one);
  HNewInstance* materialization_ins_two =
      FindSingleInstruction<HNewInstance>(graph_, materialize_two);
  std::vector<HPhi*> phis;
  std::tie(phis) = FindAllInstructions<HPhi>(graph_, set_two_and_escape);
  HPhi* new_phi = FindOrNull(
      phis.begin(), phis.end(), [&](auto p) { return p->GetType() == DataType::Type::kReference; });
  ASSERT_NE(new_phi, nullptr);
  ASSERT_NE(materialization_ins_one, nullptr);
  ASSERT_NE(materialization_ins_two, nullptr);
  EXPECT_INS_EQ(materialization_ins_one, new_phi->InputAt(0));
  EXPECT_INS_EQ(materialization_ins_two, new_phi->InputAt(1));

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_TRUE(pred_get->GetTarget()->IsPhi());
  EXPECT_INS_EQ(pred_get->GetTarget()->InputAt(0), new_phi);
  EXPECT_INS_EQ(pred_get->GetTarget()->InputAt(1), graph_->GetNullConstant());

  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(0), c0);
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(1), c10);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   obj.field = 1;
//   escape(obj);
//   return obj.field;
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
//   return obj.field;
// }
// EXIT
TEST_F(LoadStoreEliminationTest, PartialLoadElimination3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList(
      "entry",
      "exit",
      {{"entry", "left"}, {"entry", "right"}, {"left", "exit"}, {"right", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* read_left = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_left = new (GetAllocator()) HReturn(read_left);
  left->AddInstruction(write_left);
  left->AddInstruction(call_left);
  left->AddInstruction(read_left);
  left->AddInstruction(return_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* read_right = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_right = new (GetAllocator()) HReturn(read_right);
  right->AddInstruction(write_right);
  right->AddInstruction(read_right);
  right->AddInstruction(return_right);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(read_right);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(read_left);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   obj.field = 1;
//   while (true) {
//     bool esc = escape(obj);
//     // DO NOT ELIMINATE
//     obj.field = 3;
//     if (esc) break;
//   }
//   // ELIMINATE.
//   return obj.field;
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
//   return obj.field;
// }
// EXIT
TEST_F(LoadStoreEliminationTest, PartialLoadElimination4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "entry_post"},
                                                  {"entry_post", "right"},
                                                  {"right", "exit"},
                                                  {"entry_post", "left_pre"},
                                                  {"left_pre", "left_loop"},
                                                  {"left_loop", "left_loop"},
                                                  {"left_loop", "left_finish"},
                                                  {"left_finish", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(entry_post);
  GET_BLOCK(exit);
  GET_BLOCK(left_pre);
  GET_BLOCK(left_loop);
  GET_BLOCK(left_finish);
  GET_BLOCK(right);
#undef GET_BLOCK
  // Left-loops first successor is the break.
  if (left_loop->GetSuccessors()[0] != left_finish) {
    left_loop->SwapSuccessors();
  }
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* goto_entry = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(goto_entry);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry_post->AddInstruction(if_inst);

  HInstruction* write_left_pre = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left_pre = new (GetAllocator()) HGoto();
  left_pre->AddInstruction(write_left_pre);
  left_pre->AddInstruction(goto_left_pre);

  HInstruction* suspend_left_loop = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_left_loop = MakeInvoke(DataType::Type::kBool, { new_inst });
  HInstruction* write_left_loop = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_left_loop = new (GetAllocator()) HIf(call_left_loop);
  left_loop->AddInstruction(suspend_left_loop);
  left_loop->AddInstruction(call_left_loop);
  left_loop->AddInstruction(write_left_loop);
  left_loop->AddInstruction(if_left_loop);
  suspend_left_loop->CopyEnvironmentFrom(cls->GetEnvironment());
  call_left_loop->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_left_end = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_left_end = new (GetAllocator()) HReturn(read_left_end);
  left_finish->AddInstruction(read_left_end);
  left_finish->AddInstruction(return_left_end);

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* read_right = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_right = new (GetAllocator()) HReturn(read_right);
  right->AddInstruction(write_right);
  right->AddInstruction(read_right);
  right->AddInstruction(return_right);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_RETAINED(write_left_pre);
  EXPECT_INS_REMOVED(read_right);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(write_left_loop);
  EXPECT_INS_RETAINED(call_left_loop);
  EXPECT_INS_REMOVED(read_left_end);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
//   obj.field = 1;
// } else {
//   // RIGHT
//   // obj hasn't escaped so it's invisible.
//   // ELIMINATE
//   obj.field = 2;
//   noescape();
// }
// EXIT
// ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoadElimination5) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(write_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_right = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(call_right);
  right->AddInstruction(goto_right);
  call_right->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_right);
}

// // ENTRY
// obj = new Obj();
// // Eliminate this one. Object hasn't escaped yet so it's safe.
// obj.field = 3;
// noescape();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   obj.field = 5;
//   escape(obj);
//   obj.field = 1;
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// ELIMINATE
// return obj.fid
TEST_F(LoadStoreEliminationTest, PartialLoadElimination6) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* call_entry = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(call_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());
  call_entry->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left_start = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(write_left_start);
  left->AddInstruction(call_left);
  left->AddInstruction(write_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSE();

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_REMOVED(write_entry);
  EXPECT_INS_RETAINED(write_left_start);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_entry);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   obj.field = 1;
//   while (true) {
//     bool esc = escape(obj);
//     if (esc) break;
//     // DO NOT ELIMINATE
//     obj.field = 3;
//   }
// } else {
//   // RIGHT
//   // DO NOT ELIMINATE
//   obj.field = 2;
// }
// // DO NOT ELIMINATE
// return obj.field;
// EXIT
TEST_F(LoadStoreEliminationTest, PartialLoadPreserved3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "entry_post"},
                                                  {"entry_post", "right"},
                                                  {"right", "return_block"},
                                                  {"entry_post", "left_pre"},
                                                  {"left_pre", "left_loop"},
                                                  {"left_loop", "left_loop_post"},
                                                  {"left_loop_post", "left_loop"},
                                                  {"left_loop", "return_block"},
                                                  {"return_block", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(entry_post);
  GET_BLOCK(exit);
  GET_BLOCK(return_block);
  GET_BLOCK(left_pre);
  GET_BLOCK(left_loop);
  GET_BLOCK(left_loop_post);
  GET_BLOCK(right);
#undef GET_BLOCK
  // Left-loops first successor is the break.
  if (left_loop->GetSuccessors()[0] != return_block) {
    left_loop->SwapSuccessors();
  }
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* goto_entry = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(goto_entry);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry_post->AddInstruction(if_inst);

  HInstruction* write_left_pre = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left_pre = new (GetAllocator()) HGoto();
  left_pre->AddInstruction(write_left_pre);
  left_pre->AddInstruction(goto_left_pre);

  HInstruction* suspend_left_loop = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_left_loop = MakeInvoke(DataType::Type::kBool, { new_inst });
  HInstruction* if_left_loop = new (GetAllocator()) HIf(call_left_loop);
  left_loop->AddInstruction(suspend_left_loop);
  left_loop->AddInstruction(call_left_loop);
  left_loop->AddInstruction(if_left_loop);
  suspend_left_loop->CopyEnvironmentFrom(cls->GetEnvironment());
  call_left_loop->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left_loop = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_left_loop = new (GetAllocator()) HGoto();
  left_loop_post->AddInstruction(write_left_loop);
  left_loop_post->AddInstruction(goto_left_loop);

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_return = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_final = new (GetAllocator()) HReturn(read_return);
  return_block->AddInstruction(read_return);
  return_block->AddInstruction(return_final);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSENoPartial();

  EXPECT_INS_RETAINED(write_left_pre) << *write_left_pre;
  EXPECT_INS_RETAINED(read_return) << *read_return;
  EXPECT_INS_RETAINED(write_right) << *write_right;
  EXPECT_INS_RETAINED(write_left_loop) << *write_left_loop;
  EXPECT_INS_RETAINED(call_left_loop) << *call_left_loop;
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // ELIMINATE (not visible since always overridden by obj.field = 3)
//   obj.field = 1;
//   while (true) {
//     bool stop = should_stop();
//     // DO NOT ELIMINATE (visible by read at end)
//     obj.field = 3;
//     if (stop) break;
//   }
// } else {
//   // RIGHT
//   // DO NOT ELIMINATE
//   obj.field = 2;
//   escape(obj);
// }
// // DO NOT ELIMINATE
// return obj.field;
// EXIT
TEST_F(LoadStoreEliminationTest, PartialLoadPreserved4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "entry_post"},
                                                  {"entry_post", "right"},
                                                  {"right", "return_block"},
                                                  {"entry_post", "left_pre"},
                                                  {"left_pre", "left_loop"},
                                                  {"left_loop", "left_loop"},
                                                  {"left_loop", "return_block"},
                                                  {"return_block", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(entry_post);
  GET_BLOCK(exit);
  GET_BLOCK(return_block);
  GET_BLOCK(left_pre);
  GET_BLOCK(left_loop);
  GET_BLOCK(right);
#undef GET_BLOCK
  // Left-loops first successor is the break.
  if (left_loop->GetSuccessors()[0] != return_block) {
    left_loop->SwapSuccessors();
  }
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* goto_entry = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(goto_entry);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry_post->AddInstruction(if_inst);

  HInstruction* write_left_pre = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left_pre = new (GetAllocator()) HGoto();
  left_pre->AddInstruction(write_left_pre);
  left_pre->AddInstruction(goto_left_pre);

  HInstruction* suspend_left_loop = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_left_loop = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* write_left_loop = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_left_loop = new (GetAllocator()) HIf(call_left_loop);
  left_loop->AddInstruction(suspend_left_loop);
  left_loop->AddInstruction(call_left_loop);
  left_loop->AddInstruction(write_left_loop);
  left_loop->AddInstruction(if_left_loop);
  suspend_left_loop->CopyEnvironmentFrom(cls->GetEnvironment());
  call_left_loop->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_right = MakeInvoke(DataType::Type::kBool, { new_inst });
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(call_right);
  right->AddInstruction(goto_right);
  call_right->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_return = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_final = new (GetAllocator()) HReturn(read_return);
  return_block->AddInstruction(read_return);
  return_block->AddInstruction(return_final);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSENoPartial();

  EXPECT_INS_RETAINED(read_return);
  EXPECT_INS_RETAINED(write_right);
  EXPECT_INS_RETAINED(write_left_loop);
  EXPECT_INS_RETAINED(call_left_loop);
  EXPECT_INS_REMOVED(write_left_pre);
  EXPECT_INS_RETAINED(call_right);
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
//   obj.field = 1;
//   // obj has already escaped so can't use field = 1 for value
//   noescape();
// } else {
//   // RIGHT
//   // obj is needed for read since we don't know what the left value is
//   // DO NOT ELIMINATE
//   obj.field = 2;
//   noescape();
// }
// EXIT
// ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoadPreserved5) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call2_left = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(write_left);
  left->AddInstruction(call2_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());
  call2_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_right = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(call_right);
  right->AddInstruction(goto_right);
  call_right->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  PerformLSENoPartial();

  EXPECT_INS_RETAINED(read_bottom);
  EXPECT_INS_RETAINED(write_right);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_right);
}

// // ENTRY
// obj = new Obj();
// DO NOT ELIMINATE. Kept by escape.
// obj.field = 3;
// noescape();
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
//   obj.field = 1;
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoadPreserved6) {
  CreateGraph();
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* call_entry = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(call_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());
  call_entry->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* write_left = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(write_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();

  LOG(INFO) << "Pre LSE " << blks;
  PerformLSENoPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(write_entry);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_entry);
}

// // ENTRY
// // MOVED TO MATERIALIZATION BLOCK
// obj = new Obj();
// ELIMINATE, moved to materialization block. Kept by escape.
// obj.field = 3;
// // Make sure this graph isn't broken
// if (obj ==/!= (STATIC.VALUE|obj|null)) {
//   // partial_BLOCK
//   // REMOVE (either from unreachable or normal PHI creation)
//   obj.field = 4;
// }
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// PREDICATED GET
// return obj.field
TEST_P(PartialComparisonTestGroup, PartialComparisonBeforeCohort) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "critical_break"},
                                                  {"entry", "partial"},
                                                  {"partial", "merge"},
                                                  {"critical_break", "merge"},
                                                  {"merge", "left"},
                                                  {"merge", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(merge);
  GET_BLOCK(partial);
  GET_BLOCK(critical_break);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c4 = graph_->GetIntConstant(4);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  ComparisonInstructions cmp_instructions = GetComparisonInstructions(new_inst);
  HInstruction* if_inst = new (GetAllocator()) HIf(cmp_instructions.cmp_);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  cmp_instructions.AddSetup(entry);
  entry->AddInstruction(cmp_instructions.cmp_);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  cmp_instructions.AddEnvironment(cls->GetEnvironment());
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_partial = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* goto_partial = new (GetAllocator()) HGoto();
  partial->AddInstruction(write_partial);
  partial->AddInstruction(goto_partial);

  HInstruction* goto_crit_break = new (GetAllocator()) HGoto();
  critical_break->AddInstruction(goto_crit_break);

  HInstruction* if_merge = new (GetAllocator()) HIf(bool_value);
  merge->AddInstruction(if_merge);

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();

  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  std::vector<HPhi*> merges;
  HPredicatedInstanceFieldGet* pred_get;
  HInstanceFieldSet* init_set;
  std::tie(pred_get, init_set) =
      FindSingleInstructions<HPredicatedInstanceFieldGet, HInstanceFieldSet>(graph_);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_);
  ASSERT_EQ(merges.size(), 3u);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() == breturn;
  });
  HPhi* merge_value_top = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() != breturn;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_entry);
  EXPECT_INS_REMOVED(write_partial);
  EXPECT_INS_RETAINED(call_left);
  CheckFinalInstruction(if_inst->InputAt(0), ComparisonPlacement::kBeforeEscape);
  EXPECT_INS_EQ(init_set->InputAt(1), merge_value_top);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return);
}

// // ENTRY
// // MOVED TO MATERIALIZATION BLOCK
// obj = new Obj();
// ELIMINATE, moved to materialization block. Kept by escape.
// obj.field = 3;
// // Make sure this graph isn't broken
// if (parameter_value) {
//   if (obj ==/!= (STATIC.VALUE|obj|null)) {
//     // partial_BLOCK
//     obj.field = 4;
//   }
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// PREDICATED GET
// return obj.field
TEST_P(PartialComparisonTestGroup, PartialComparisonInCohortBeforeEscape) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left_begin"},
                                                  {"left_begin", "partial"},
                                                  {"left_begin", "left_crit_break"},
                                                  {"left_crit_break", "left"},
                                                  {"partial", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(partial);
  GET_BLOCK(left_begin);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(left_crit_break);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(left, {left_crit_break, partial});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c4 = graph_->GetIntConstant(4);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  ComparisonInstructions cmp_instructions = GetComparisonInstructions(new_inst);
  HInstruction* if_left_begin = new (GetAllocator()) HIf(cmp_instructions.cmp_);
  cmp_instructions.AddSetup(left_begin);
  left_begin->AddInstruction(cmp_instructions.cmp_);
  left_begin->AddInstruction(if_left_begin);
  cmp_instructions.AddEnvironment(cls->GetEnvironment());

  left_crit_break->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* write_partial = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* goto_partial = new (GetAllocator()) HGoto();
  partial->AddInstruction(write_partial);
  partial->AddInstruction(goto_partial);

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  std::vector<HPhi*> merges;
  HInstanceFieldSet* init_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, left_begin->GetSinglePredecessor());
  HInstanceFieldSet* partial_set = FindSingleInstruction<HInstanceFieldSet>(graph_, partial);
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_);
  ASSERT_EQ(merges.size(), 2u);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  EXPECT_EQ(merge_value_return->GetBlock(), breturn)
      << blks.GetName(merge_value_return->GetBlock());
  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_entry);
  EXPECT_INS_RETAINED(write_partial);
  EXPECT_INS_RETAINED(call_left);
  CheckFinalInstruction(if_left_begin->InputAt(0), ComparisonPlacement::kInEscape);
  EXPECT_INS_EQ(init_set->InputAt(1), c3);
  EXPECT_INS_EQ(partial_set->InputAt(0), init_set->InputAt(0));
  EXPECT_INS_EQ(partial_set->InputAt(1), c4);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return);
}

// // ENTRY
// // MOVED TO MATERIALIZATION BLOCK
// obj = new Obj();
// ELIMINATE, moved to materialization block. Kept by escape.
// obj.field = 3;
// // Make sure this graph isn't broken
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// if (obj ==/!= (STATIC.VALUE|obj|null)) {
//   // partial_BLOCK
//   obj.field = 4;
// }
// EXIT
// PREDICATED GET
// return obj.field
TEST_P(PartialComparisonTestGroup, PartialComparisonAfterCohort) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "merge"},
                                                  {"right", "merge"},
                                                  {"merge", "critical_break"},
                                                  {"critical_break", "breturn"},
                                                  {"merge", "partial"},
                                                  {"partial", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(partial);
  GET_BLOCK(critical_break);
  GET_BLOCK(merge);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {critical_break, partial});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c4 = graph_->GetIntConstant(4);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  ComparisonInstructions cmp_instructions = GetComparisonInstructions(new_inst);
  HInstruction* if_merge = new (GetAllocator()) HIf(cmp_instructions.cmp_);
  cmp_instructions.AddSetup(merge);
  merge->AddInstruction(cmp_instructions.cmp_);
  merge->AddInstruction(if_merge);
  cmp_instructions.AddEnvironment(cls->GetEnvironment());

  HInstanceFieldSet* write_partial = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* goto_partial = new (GetAllocator()) HGoto();
  partial->AddInstruction(write_partial);
  partial->AddInstruction(goto_partial);

  HInstruction* goto_crit_break = new (GetAllocator()) HGoto();
  critical_break->AddInstruction(goto_crit_break);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  std::vector<HPhi*> merges;
  HInstanceFieldSet* init_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, left->GetSinglePredecessor());
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_);
  ASSERT_EQ(merges.size(), 3u);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() == breturn;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_entry);
  EXPECT_INS_RETAINED(write_partial);
  EXPECT_TRUE(write_partial->GetIsPredicatedSet());
  EXPECT_INS_RETAINED(call_left);
  CheckFinalInstruction(if_merge->InputAt(0), ComparisonPlacement::kAfterEscape);
  EXPECT_INS_EQ(init_set->InputAt(1), c3);
  ASSERT_TRUE(write_partial->InputAt(0)->IsPhi());
  EXPECT_INS_EQ(write_partial->InputAt(0)->AsPhi()->InputAt(0), init_set->InputAt(0));
  EXPECT_INS_EQ(write_partial->InputAt(1), c4);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return);
}

// // ENTRY
// // MOVED TO MATERIALIZATION BLOCK
// obj = new Obj();
// ELIMINATE, moved to materialization block. Kept by escape.
// obj.field = 3;
// // Make sure this graph isn't broken
// if (parameter_value) {
//   // LEFT
//   // DO NOT ELIMINATE
//   escape(obj);
//   if (obj ==/!= (STATIC.VALUE|obj|null)) {
//     // partial_BLOCK
//     obj.field = 4;
//   }
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// PREDICATED GET
// return obj.field
TEST_P(PartialComparisonTestGroup, PartialComparisonInCohortAfterEscape) {
  PartialComparisonKind kind = GetParam();
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"left", "partial"},
                                                  {"partial", "left_end"},
                                                  {"left", "left_crit_break"},
                                                  {"left_crit_break", "left_end"},
                                                  {"left_end", "breturn"},
                                                  {"entry", "right"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(partial);
  GET_BLOCK(left_end);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(left_crit_break);
  GET_BLOCK(right);
#undef GET_BLOCK
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c4 = graph_->GetIntConstant(4);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  ComparisonInstructions cmp_instructions = GetComparisonInstructions(new_inst);
  HInstruction* if_left = new (GetAllocator()) HIf(cmp_instructions.cmp_);
  left->AddInstruction(call_left);
  cmp_instructions.AddSetup(left);
  left->AddInstruction(cmp_instructions.cmp_);
  left->AddInstruction(if_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());
  cmp_instructions.AddEnvironment(cls->GetEnvironment());
  if (if_left->AsIf()->IfTrueSuccessor() != partial) {
    left->SwapSuccessors();
  }

  HInstruction* write_partial = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* goto_partial = new (GetAllocator()) HGoto();
  partial->AddInstruction(write_partial);
  partial->AddInstruction(goto_partial);

  HInstruction* goto_left_crit_break = new (GetAllocator()) HGoto();
  left_crit_break->AddInstruction(goto_left_crit_break);

  HInstruction* goto_left_end = new (GetAllocator()) HGoto();
  left_end->AddInstruction(goto_left_end);

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();

  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  std::vector<HPhi*> merges;
  std::vector<HInstanceFieldSet*> sets;
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_);
  std::tie(merges, sets) = FindAllInstructions<HPhi, HInstanceFieldSet>(graph_);
  ASSERT_EQ(merges.size(), 2u);
  ASSERT_EQ(sets.size(), 2u);
  HInstanceFieldSet* init_set = FindOrNull(sets.begin(), sets.end(), [&](HInstanceFieldSet* s) {
    return s->GetBlock()->GetSingleSuccessor() == left;
  });
  EXPECT_INS_EQ(init_set->InputAt(1), c3);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() == breturn;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_entry);
  if (kind.IsPossiblyTrue()) {
    EXPECT_INS_RETAINED(write_partial);
    EXPECT_TRUE(std::find(sets.begin(), sets.end(), write_partial) != sets.end());
  }
  EXPECT_INS_RETAINED(call_left);
  CheckFinalInstruction(if_left->InputAt(0), ComparisonPlacement::kInEscape);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return);
}

INSTANTIATE_TEST_SUITE_P(
    LoadStoreEliminationTest,
    PartialComparisonTestGroup,
    testing::Values(PartialComparisonKind{PartialComparisonKind::Type::kEquals,
                                          PartialComparisonKind::Target::kNull,
                                          PartialComparisonKind::Position::kLeft},
                    PartialComparisonKind{PartialComparisonKind::Type::kEquals,
                                          PartialComparisonKind::Target::kNull,
                                          PartialComparisonKind::Position::kRight},
                    PartialComparisonKind{PartialComparisonKind::Type::kEquals,
                                          PartialComparisonKind::Target::kValue,
                                          PartialComparisonKind::Position::kLeft},
                    PartialComparisonKind{PartialComparisonKind::Type::kEquals,
                                          PartialComparisonKind::Target::kValue,
                                          PartialComparisonKind::Position::kRight},
                    PartialComparisonKind{PartialComparisonKind::Type::kEquals,
                                          PartialComparisonKind::Target::kSelf,
                                          PartialComparisonKind::Position::kLeft},
                    PartialComparisonKind{PartialComparisonKind::Type::kNotEquals,
                                          PartialComparisonKind::Target::kNull,
                                          PartialComparisonKind::Position::kLeft},
                    PartialComparisonKind{PartialComparisonKind::Type::kNotEquals,
                                          PartialComparisonKind::Target::kNull,
                                          PartialComparisonKind::Position::kRight},
                    PartialComparisonKind{PartialComparisonKind::Type::kNotEquals,
                                          PartialComparisonKind::Target::kSelf,
                                          PartialComparisonKind::Position::kLeft},
                    PartialComparisonKind{PartialComparisonKind::Type::kNotEquals,
                                          PartialComparisonKind::Target::kValue,
                                          PartialComparisonKind::Position::kLeft},
                    PartialComparisonKind{PartialComparisonKind::Type::kNotEquals,
                                          PartialComparisonKind::Target::kValue,
                                          PartialComparisonKind::Position::kRight}));

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// predicated-ELIMINATE
// obj.field = 3;
TEST_F(LoadStoreEliminationTest, PredicatedStore1) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  InitGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* write_bottom = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturnVoid();
  breturn->AddInstruction(write_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();

  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_RETAINED(write_bottom);
  EXPECT_TRUE(write_bottom->AsInstanceFieldSet()->GetIsPredicatedSet());
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(call_left);
  HPhi* merge_alloc = FindSingleInstruction<HPhi>(graph_, breturn);
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << *merge_alloc;
  EXPECT_EQ(merge_alloc->InputAt(0)->InputAt(0), cls) << *merge_alloc << " cls? " << *cls;
  EXPECT_EQ(merge_alloc->InputAt(1), null_const);
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// // MERGE
// if (second_param) {
//   // NON_ESCAPE
//   obj.field = 1;
//   noescape();
// }
// EXIT
// predicated-ELIMINATE
// obj.field = 4;
TEST_F(LoadStoreEliminationTest, PredicatedStore2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "merge"},
                                                  {"right", "merge"},
                                                  {"merge", "non_escape"},
                                                  {"non_escape", "breturn"},
                                                  {"merge", "merge_crit_break"},
                                                  {"merge_crit_break", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(merge);
  GET_BLOCK(merge_crit_break);
  GET_BLOCK(non_escape);
#undef GET_BLOCK
  EnsurePredecessorOrder(merge, {left, right});
  EnsurePredecessorOrder(breturn, {merge_crit_break, non_escape});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* bool_value2 = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c1 = graph_->GetIntConstant(3);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c4 = graph_->GetIntConstant(4);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* merge_if = new (GetAllocator()) HIf(bool_value2);
  merge->AddInstruction(merge_if);

  merge_crit_break->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* write_non_escape = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* non_escape_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* non_escape_goto = new (GetAllocator()) HGoto();
  non_escape->AddInstruction(write_non_escape);
  non_escape->AddInstruction(non_escape_call);
  non_escape->AddInstruction(non_escape_goto);
  non_escape_call->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_bottom = MakeIFieldSet(new_inst, c4, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturnVoid();
  breturn->AddInstruction(write_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_RETAINED(write_bottom);
  EXPECT_TRUE(write_bottom->AsInstanceFieldSet()->GetIsPredicatedSet()) << *write_bottom;
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(call_left);
  HInstanceFieldSet* pred_set = FindSingleInstruction<HInstanceFieldSet>(graph_, breturn);
  HPhi* merge_alloc = FindSingleInstruction<HPhi>(graph_);
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << *merge_alloc;
  EXPECT_INS_EQ(merge_alloc->InputAt(0)->InputAt(0), cls) << " phi is: " << *merge_alloc;
  EXPECT_INS_EQ(merge_alloc->InputAt(1), null_const);
  ASSERT_NE(pred_set, nullptr);
  EXPECT_TRUE(pred_set->GetIsPredicatedSet()) << *pred_set;
  EXPECT_INS_EQ(pred_set->InputAt(0), merge_alloc);
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// EXIT
// predicated-ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PredicatedLoad1) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(call_left);
  std::vector<HPhi*> merges;
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_, breturn);
  ASSERT_EQ(merges.size(), 2u);
  HPhi* merge_value_return = FindOrNull(
      merges.begin(), merges.end(), [](HPhi* p) { return p->GetType() == DataType::Type::kInt32; });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << *merge_alloc;
  EXPECT_EQ(merge_alloc->InputAt(0)->InputAt(0), cls) << *merge_alloc << " cls? " << *cls;
  EXPECT_EQ(merge_alloc->InputAt(1), null_const);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return) << " pred-get is: " << *pred_get;
  EXPECT_INS_EQ(merge_value_return->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return;
  EXPECT_INS_EQ(merge_value_return->InputAt(1), c2) << " merge val is: " << *merge_value_return;
}

// // ENTRY
// obj1 = new Obj1();
// obj2 = new Obj2();
// obj1.field = 3;
// obj2.field = 13;
// if (parameter_value) {
//   // LEFT
//   escape(obj1);
//   escape(obj2);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj1.field = 2;
//   obj2.field = 12;
// }
// EXIT
// predicated-ELIMINATE
// return obj1.field + obj2.field
TEST_F(LoadStoreEliminationTest, MultiPredicatedLoad1) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c13 = graph_->GetIntConstant(13);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* write_entry1 = MakeIFieldSet(new_inst1, c3, MemberOffset(32));
  HInstruction* write_entry2 = MakeIFieldSet(new_inst2, c13, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls1);
  entry->AddInstruction(cls2);
  entry->AddInstruction(new_inst1);
  entry->AddInstruction(new_inst2);
  entry->AddInstruction(write_entry1);
  entry->AddInstruction(write_entry2);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls1, {});
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst2->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* call_left1 = MakeInvoke(DataType::Type::kVoid, { new_inst1 });
  HInstruction* call_left2 = MakeInvoke(DataType::Type::kVoid, { new_inst2 });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left1);
  left->AddInstruction(call_left2);
  left->AddInstruction(goto_left);
  call_left1->CopyEnvironmentFrom(cls1->GetEnvironment());
  call_left2->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* write_right1 = MakeIFieldSet(new_inst1, c2, MemberOffset(32));
  HInstruction* write_right2 = MakeIFieldSet(new_inst2, c12, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right1);
  right->AddInstruction(write_right2);
  right->AddInstruction(goto_right);

  HInstruction* read_bottom1 = MakeIFieldGet(new_inst1, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* read_bottom2 = MakeIFieldGet(new_inst2, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* combine =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, read_bottom1, read_bottom2);
  HInstruction* return_exit = new (GetAllocator()) HReturn(combine);
  breturn->AddInstruction(read_bottom1);
  breturn->AddInstruction(read_bottom2);
  breturn->AddInstruction(combine);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom1);
  EXPECT_INS_REMOVED(read_bottom2);
  EXPECT_INS_REMOVED(write_right1);
  EXPECT_INS_REMOVED(write_right2);
  EXPECT_INS_RETAINED(call_left1);
  EXPECT_INS_RETAINED(call_left2);
  std::vector<HPhi*> merges;
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::tie(merges, pred_gets) =
      FindAllInstructions<HPhi, HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_EQ(merges.size(), 4u);
  ASSERT_EQ(pred_gets.size(), 2u);
  HPhi* merge_value_return1 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->InputAt(1) == c2;
  });
  HPhi* merge_value_return2 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->InputAt(1) == c12;
  });
  HPhi* merge_alloc1 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kReference &&
           p->InputAt(0)->IsNewInstance() &&
           p->InputAt(0)->InputAt(0) == cls1;
  });
  HPhi* merge_alloc2 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kReference &&
           p->InputAt(0)->IsNewInstance() &&
           p->InputAt(0)->InputAt(0) == cls2;
  });
  ASSERT_NE(merge_alloc1, nullptr);
  ASSERT_NE(merge_alloc2, nullptr);
  EXPECT_EQ(merge_alloc1->InputAt(1), graph_->GetNullConstant());
  EXPECT_EQ(merge_alloc2->InputAt(1), graph_->GetNullConstant());
  HPredicatedInstanceFieldGet* pred_get1 =
      FindOrNull(pred_gets.begin(), pred_gets.end(), [&](HPredicatedInstanceFieldGet* pg) {
        return pg->GetTarget() == merge_alloc1;
      });
  HPredicatedInstanceFieldGet* pred_get2 =
      FindOrNull(pred_gets.begin(), pred_gets.end(), [&](HPredicatedInstanceFieldGet* pg) {
        return pg->GetTarget() == merge_alloc2;
      });
  ASSERT_NE(pred_get1, nullptr);
  EXPECT_INS_EQ(pred_get1->GetTarget(), merge_alloc1);
  EXPECT_INS_EQ(pred_get1->GetDefaultValue(), merge_value_return1)
      << " pred-get is: " << *pred_get1;
  EXPECT_INS_EQ(merge_value_return1->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(merge_value_return1->InputAt(1), c2) << " merge val is: " << *merge_value_return1;
  ASSERT_NE(pred_get2, nullptr);
  EXPECT_INS_EQ(pred_get2->GetTarget(), merge_alloc2);
  EXPECT_INS_EQ(pred_get2->GetDefaultValue(), merge_value_return2)
      << " pred-get is: " << *pred_get2;
  EXPECT_INS_EQ(merge_value_return2->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(merge_value_return2->InputAt(1), c12) << " merge val is: " << *merge_value_return1;
}

// // ENTRY
// obj1 = new Obj1();
// obj2 = new Obj2();
// obj1.field = 3;
// obj2.field = 13;
// if (parameter_value) {
//   // LEFT
//   escape(obj1);
//   // ELIMINATE
//   obj2.field = 12;
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj1.field = 2;
//   escape(obj2);
// }
// EXIT
// predicated-ELIMINATE
// return obj1.field + obj2.field
TEST_F(LoadStoreEliminationTest, MultiPredicatedLoad2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c12 = graph_->GetIntConstant(12);
  HInstruction* c13 = graph_->GetIntConstant(13);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* write_entry1 = MakeIFieldSet(new_inst1, c3, MemberOffset(32));
  HInstruction* write_entry2 = MakeIFieldSet(new_inst2, c13, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls1);
  entry->AddInstruction(cls2);
  entry->AddInstruction(new_inst1);
  entry->AddInstruction(new_inst2);
  entry->AddInstruction(write_entry1);
  entry->AddInstruction(write_entry2);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls1, {});
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst2->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* call_left1 = MakeInvoke(DataType::Type::kVoid, { new_inst1 });
  HInstruction* write_left2 = MakeIFieldSet(new_inst2, c12, MemberOffset(32));
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left1);
  left->AddInstruction(write_left2);
  left->AddInstruction(goto_left);
  call_left1->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* write_right1 = MakeIFieldSet(new_inst1, c2, MemberOffset(32));
  HInstruction* call_right2 = MakeInvoke(DataType::Type::kVoid, { new_inst2 });
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right1);
  right->AddInstruction(call_right2);
  right->AddInstruction(goto_right);
  call_right2->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* read_bottom1 = MakeIFieldGet(new_inst1, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* read_bottom2 = MakeIFieldGet(new_inst2, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* combine =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, read_bottom1, read_bottom2);
  HInstruction* return_exit = new (GetAllocator()) HReturn(combine);
  breturn->AddInstruction(read_bottom1);
  breturn->AddInstruction(read_bottom2);
  breturn->AddInstruction(combine);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom1);
  EXPECT_INS_REMOVED(read_bottom2);
  EXPECT_INS_REMOVED(write_right1);
  EXPECT_INS_REMOVED(write_left2);
  EXPECT_INS_RETAINED(call_left1);
  EXPECT_INS_RETAINED(call_right2);
  std::vector<HPhi*> merges;
  std::vector<HPredicatedInstanceFieldGet*> pred_gets;
  std::tie(merges, pred_gets) =
      FindAllInstructions<HPhi, HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_EQ(merges.size(), 4u);
  ASSERT_EQ(pred_gets.size(), 2u);
  HPhi* merge_value_return1 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->InputAt(1) == c2;
  });
  HPhi* merge_value_return2 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->InputAt(0) == c12;
  });
  HPhi* merge_alloc1 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kReference && p->InputAt(1)->IsNullConstant();
  });
  HPhi* merge_alloc2 = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kReference && p->InputAt(0)->IsNullConstant();
  });
  ASSERT_NE(merge_alloc1, nullptr);
  ASSERT_NE(merge_alloc2, nullptr);
  EXPECT_TRUE(merge_alloc1->InputAt(0)->IsNewInstance()) << *merge_alloc1;
  EXPECT_INS_EQ(merge_alloc1->InputAt(0)->InputAt(0), cls1) << *merge_alloc1;
  EXPECT_INS_EQ(merge_alloc1->InputAt(1), graph_->GetNullConstant());
  EXPECT_TRUE(merge_alloc2->InputAt(1)->IsNewInstance()) << *merge_alloc2;
  EXPECT_INS_EQ(merge_alloc2->InputAt(1)->InputAt(0), cls2) << *merge_alloc2;
  EXPECT_INS_EQ(merge_alloc2->InputAt(0), graph_->GetNullConstant());
  HPredicatedInstanceFieldGet* pred_get1 =
      FindOrNull(pred_gets.begin(), pred_gets.end(), [&](HPredicatedInstanceFieldGet* pg) {
        return pg->GetTarget() == merge_alloc1;
      });
  HPredicatedInstanceFieldGet* pred_get2 =
      FindOrNull(pred_gets.begin(), pred_gets.end(), [&](HPredicatedInstanceFieldGet* pg) {
        return pg->GetTarget() == merge_alloc2;
      });
  ASSERT_NE(pred_get1, nullptr);
  EXPECT_INS_EQ(pred_get1->GetTarget(), merge_alloc1);
  EXPECT_INS_EQ(pred_get1->GetDefaultValue(), merge_value_return1)
      << " pred-get is: " << *pred_get1;
  EXPECT_INS_EQ(merge_value_return1->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(merge_value_return1->InputAt(1), c2) << " merge val is: " << *merge_value_return1;
  ASSERT_NE(pred_get2, nullptr);
  EXPECT_INS_EQ(pred_get2->GetTarget(), merge_alloc2);
  EXPECT_INS_EQ(pred_get2->GetDefaultValue(), merge_value_return2)
      << " pred-get is: " << *pred_get2;
  EXPECT_INS_EQ(merge_value_return2->InputAt(1), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return1;
  EXPECT_INS_EQ(merge_value_return2->InputAt(0), c12) << " merge val is: " << *merge_value_return1;
}

// Based on structure seen in `java.util.List
// java.util.Collections.checkedList(java.util.List, java.lang.Class)`
// Incorrect accounting would cause attempts to materialize both obj1 and obj2
// in each of the materialization blocks.
// // ENTRY
// Obj obj;
// if (param1) {
//   // needs to be moved after param2 check
//   obj1 = new Obj1();
//   obj1.foo = 33;
//   if (param2) {
//     return obj1.foo;
//   }
//   obj = obj1;
// } else {
//   obj2 = new Obj2();
//   obj2.foo = 44;
//   if (param2) {
//     return obj2.foo;
//   }
//   obj = obj2;
// }
// EXIT
// // obj = PHI[obj1, obj2]
// // NB The phi acts as an escape for both obj1 and obj2 meaning as far as the
// // LSA is concerned the escape frontier is left_crit_break->breturn and
// // right_crit_break->breturn for both even though only one of the objects is
// // actually live at each edge.
// // TODO In the future we really should track liveness through PHIs which would
// // allow us to entirely remove the allocation in this test.
// return obj.foo;
TEST_F(LoadStoreEliminationTest, MultiPredicatedLoad3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"left", "left_end"},
                                                  {"left_end", "breturn"},
                                                  {"left", "left_exit_early"},
                                                  {"left_exit_early", "exit"},
                                                  {"entry", "right"},
                                                  {"right", "right_end"},
                                                  {"right_end", "breturn"},
                                                  {"right", "right_exit_early"},
                                                  {"right_exit_early", "exit"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(left_end);
  GET_BLOCK(left_exit_early);
  GET_BLOCK(right);
  GET_BLOCK(right_end);
  GET_BLOCK(right_exit_early);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left_end, right_end});
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);
  HInstruction* c33 = graph_->GetIntConstant(33);
  HInstruction* c44 = graph_->GetIntConstant(44);

  HInstruction* if_inst = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(if_inst);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* write1 = MakeIFieldSet(new_inst1, c33, MemberOffset(32));
  HInstruction* if_left = new (GetAllocator()) HIf(param2);
  left->AddInstruction(cls1);
  left->AddInstruction(new_inst1);
  left->AddInstruction(write1);
  left->AddInstruction(if_left);
  ManuallyBuildEnvFor(cls1, {});
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());

  left_end->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* early_exit_left_read =
      MakeIFieldGet(new_inst1, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* early_exit_left_return = new (GetAllocator()) HReturn(early_exit_left_read);
  left_exit_early->AddInstruction(early_exit_left_read);
  left_exit_early->AddInstruction(early_exit_left_return);

  HInstruction* cls2 = MakeClassLoad();
  HInstruction* new_inst2 = MakeNewInstance(cls2);
  HInstruction* write2 = MakeIFieldSet(new_inst2, c44, MemberOffset(32));
  HInstruction* if_right = new (GetAllocator()) HIf(param2);
  right->AddInstruction(cls2);
  right->AddInstruction(new_inst2);
  right->AddInstruction(write2);
  right->AddInstruction(if_right);
  cls2->CopyEnvironmentFrom(cls1->GetEnvironment());
  new_inst2->CopyEnvironmentFrom(cls2->GetEnvironment());

  right_end->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* early_exit_right_read =
      MakeIFieldGet(new_inst2, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* early_exit_right_return = new (GetAllocator()) HReturn(early_exit_right_read);
  right_exit_early->AddInstruction(early_exit_right_read);
  right_exit_early->AddInstruction(early_exit_right_return);

  HPhi* bottom_phi = MakePhi({new_inst1, new_inst2});
  HInstruction* read_bottom = MakeIFieldGet(bottom_phi, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddPhi(bottom_phi);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(early_exit_left_read);
  EXPECT_INS_REMOVED(early_exit_right_read);
  EXPECT_INS_RETAINED(bottom_phi);
  EXPECT_INS_RETAINED(read_bottom);
  EXPECT_INS_EQ(early_exit_left_return->InputAt(0), c33);
  EXPECT_INS_EQ(early_exit_right_return->InputAt(0), c44);
  // These assert there is only 1 HNewInstance in the given blocks.
  HNewInstance* moved_ni1 =
      FindSingleInstruction<HNewInstance>(graph_, left_end->GetSinglePredecessor());
  HNewInstance* moved_ni2 =
      FindSingleInstruction<HNewInstance>(graph_, right_end->GetSinglePredecessor());
  ASSERT_NE(moved_ni1, nullptr);
  ASSERT_NE(moved_ni2, nullptr);
  EXPECT_INS_EQ(bottom_phi->InputAt(0), moved_ni1);
  EXPECT_INS_EQ(bottom_phi->InputAt(1), moved_ni2);
}

// // ENTRY
// obj = new Obj();
// if (param1) {
//   obj.field = 3;
//   noescape();
// } else {
//   obj.field = 2;
//   noescape();
// }
// int abc;
// if (parameter_value) {
//   // LEFT
//   abc = 4;
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   noescape();
//   abc = obj.field + 4;
// }
// abc = phi
// EXIT
// predicated-ELIMINATE
// return obj.field + abc
TEST_F(LoadStoreEliminationTest, PredicatedLoad4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "start_left"},
                                                  {"entry", "start_right"},
                                                  {"start_left", "mid"},
                                                  {"start_right", "mid"},
                                                  {"mid", "left"},
                                                  {"mid", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(mid);
  GET_BLOCK(start_left);
  GET_BLOCK(start_right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  EnsurePredecessorOrder(mid, {start_left, start_right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* bool_value2 = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c4 = graph_->GetIntConstant(4);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_start_left = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* call_start_left = MakeInvoke(DataType::Type::kVoid, { });
  start_left->AddInstruction(write_start_left);
  start_left->AddInstruction(call_start_left);
  start_left->AddInstruction(new (GetAllocator()) HGoto());
  call_start_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_start_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_start_right = MakeInvoke(DataType::Type::kVoid, { });
  start_right->AddInstruction(write_start_right);
  start_right->AddInstruction(call_start_right);
  start_right->AddInstruction(new (GetAllocator()) HGoto());
  call_start_right->CopyEnvironmentFrom(cls->GetEnvironment());

  mid->AddInstruction(new (GetAllocator()) HIf(bool_value2));

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_right = MakeInvoke(DataType::Type::kVoid, { });
  HInstruction* read_right = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_right = new (GetAllocator()) HAdd(DataType::Type::kInt32, read_right, c4);
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(call_right);
  right->AddInstruction(read_right);
  right->AddInstruction(add_right);
  right->AddInstruction(goto_right);
  call_right->CopyEnvironmentFrom(cls->GetEnvironment());

  HPhi* phi_bottom = MakePhi({c4, add_right});
  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_bottom =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, read_bottom, phi_bottom);
  HInstruction* return_exit = new (GetAllocator()) HReturn(add_bottom);
  breturn->AddPhi(phi_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(add_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(read_right);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_right);
  EXPECT_INS_RETAINED(call_start_left);
  EXPECT_INS_RETAINED(call_start_right);
  std::vector<HPhi*> merges;
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_, breturn);
  ASSERT_EQ(merges.size(), 3u);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p != phi_bottom && p->GetType() == DataType::Type::kInt32;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << *merge_alloc;
  EXPECT_EQ(merge_alloc->InputAt(0)->InputAt(0), cls) << *merge_alloc << " cls? " << *cls;
  EXPECT_EQ(merge_alloc->InputAt(1), null_const);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return) << " pred-get is: " << *pred_get;
  EXPECT_INS_EQ(merge_value_return->InputAt(0), graph_->GetIntConstant(0))
      << " merge val is: " << *merge_value_return;
  EXPECT_INS_EQ(merge_value_return->InputAt(1), FindSingleInstruction<HPhi>(graph_, mid))
      << " merge val is: " << *merge_value_return;
}

// Based on structure seen in `java.util.Set java.util.Collections$UnmodifiableMap.entrySet()`
// We end up having to update a PHI generated by normal LSE.
// // ENTRY
// Obj obj_init = param_obj.BAR;
// if (param1) {
//   Obj other = new Obj();
//   other.foo = 42;
//   if (param2) {
//     return other.foo;
//   } else {
//     param_obj.BAR = other;
//   }
// } else { }
// EXIT
// LSE Turns this into PHI[obj_init, other]
// read_bottom = param_obj.BAR;
// // won't be changed. The escape happens with .BAR set so this is in escaping cohort.
// return read_bottom.foo;
TEST_F(LoadStoreEliminationTest, MultiPredicatedLoad4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"left", "left_early_return"},
                                                  {"left_early_return", "exit"},
                                                  {"left", "left_write_escape"},
                                                  {"left_write_escape", "breturn"},
                                                  {"entry", "right"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(left_early_return);
  GET_BLOCK(left_write_escape);
  GET_BLOCK(right);
#undef GET_BLOCK
  MemberOffset foo_offset = MemberOffset(32);
  MemberOffset bar_offset = MemberOffset(20);
  EnsurePredecessorOrder(breturn, {left_write_escape, right});
  HInstruction* c42 = graph_->GetIntConstant(42);
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);
  HInstruction* param_obj = MakeParam(DataType::Type::kReference);

  HInstruction* get_initial = MakeIFieldGet(param_obj, DataType::Type::kReference, bar_offset);
  HInstruction* if_inst = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(get_initial);
  entry->AddInstruction(if_inst);

  HInstruction* cls1 = MakeClassLoad();
  HInstruction* new_inst1 = MakeNewInstance(cls1);
  HInstruction* write1 = MakeIFieldSet(new_inst1, c42, foo_offset);
  HInstruction* if_left = new (GetAllocator()) HIf(param2);
  left->AddInstruction(cls1);
  left->AddInstruction(new_inst1);
  left->AddInstruction(write1);
  left->AddInstruction(if_left);
  ManuallyBuildEnvFor(cls1, {});
  new_inst1->CopyEnvironmentFrom(cls1->GetEnvironment());

  HInstruction* read_early_return = MakeIFieldGet(new_inst1, DataType::Type::kInt32, foo_offset);
  HInstruction* return_early = new (GetAllocator()) HReturn(read_early_return);
  left_early_return->AddInstruction(read_early_return);
  left_early_return->AddInstruction(return_early);

  HInstruction* write_escape = MakeIFieldSet(param_obj, new_inst1, bar_offset);
  HInstruction* write_goto = new (GetAllocator()) HGoto();
  left_write_escape->AddInstruction(write_escape);
  left_write_escape->AddInstruction(write_goto);

  right->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* read_bottom = MakeIFieldGet(param_obj, DataType::Type::kReference, bar_offset);
  HInstruction* final_read = MakeIFieldGet(read_bottom, DataType::Type::kInt32, foo_offset);
  HInstruction* return_exit = new (GetAllocator()) HReturn(final_read);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(final_read);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(read_early_return);
  EXPECT_INS_EQ(return_early->InputAt(0), c42);
  EXPECT_INS_RETAINED(final_read);
  HNewInstance* moved_ni =
      FindSingleInstruction<HNewInstance>(graph_, left_write_escape->GetSinglePredecessor());
  EXPECT_TRUE(final_read->InputAt(0)->IsPhi());
  EXPECT_INS_EQ(final_read->InputAt(0)->InputAt(0), moved_ni);
  EXPECT_INS_EQ(final_read->InputAt(0)->InputAt(1), get_initial);
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// // MERGE
// if (second_param) {
//   // NON_ESCAPE
//   obj.field = 1;
//   noescape();
// }
// EXIT
// predicated-ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PredicatedLoad2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "merge"},
                                                  {"right", "merge"},
                                                  {"merge", "non_escape"},
                                                  {"non_escape", "breturn"},
                                                  {"merge", "crit_break"},
                                                  {"crit_break", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(merge);
  GET_BLOCK(non_escape);
  GET_BLOCK(crit_break);
#undef GET_BLOCK
  EnsurePredecessorOrder(merge, {left, right});
  EnsurePredecessorOrder(breturn, {crit_break, non_escape});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* bool_value2 = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* merge_if = new (GetAllocator()) HIf(bool_value2);
  merge->AddInstruction(merge_if);

  crit_break->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* write_non_escape = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* non_escape_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* non_escape_goto = new (GetAllocator()) HGoto();
  non_escape->AddInstruction(write_non_escape);
  non_escape->AddInstruction(non_escape_call);
  non_escape->AddInstruction(non_escape_goto);
  non_escape_call->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(call_left);
  std::vector<HPhi*> merges;
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_);
  ASSERT_EQ(merges.size(), 3u);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() == breturn;
  });
  HPhi* merge_value_merge = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() != breturn;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << *merge_alloc;
  EXPECT_INS_EQ(merge_alloc->InputAt(0)->InputAt(0), cls)
      << " phi is: " << merge_alloc->DumpWithArgs();
  EXPECT_INS_EQ(merge_alloc->InputAt(1), null_const);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return)
      << "get is " << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(merge_value_return->InputAt(0), merge_value_merge)
      << " phi is: " << *merge_value_return;
  EXPECT_INS_EQ(merge_value_return->InputAt(1), c1)
      << " phi is: " << merge_value_return->DumpWithArgs();
  EXPECT_INS_EQ(merge_value_merge->InputAt(0), graph_->GetIntConstant(0))
      << " phi is: " << *merge_value_merge;
  EXPECT_INS_EQ(merge_value_merge->InputAt(1), c2)
      << " phi is: " << merge_value_merge->DumpWithArgs();
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (parameter_value) {
//   // LEFT
//   escape(obj);
// } else {
//   // RIGHT
//   // ELIMINATE
//   obj.field = 2;
// }
// // MERGE
// if (second_param) {
//   // NON_ESCAPE
//   obj.field = 1;
// }
// noescape();
// EXIT
// predicated-ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PredicatedLoad3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "merge"},
                                                  {"right", "merge"},
                                                  {"merge", "non_escape"},
                                                  {"non_escape", "breturn"},
                                                  {"merge", "crit_break"},
                                                  {"crit_break", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(merge);
  GET_BLOCK(crit_break);
  GET_BLOCK(non_escape);
#undef GET_BLOCK
  EnsurePredecessorOrder(merge, {left, right});
  EnsurePredecessorOrder(breturn, {crit_break, non_escape});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* bool_value2 = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* merge_if = new (GetAllocator()) HIf(bool_value2);
  merge->AddInstruction(merge_if);

  HInstruction* write_non_escape = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* non_escape_goto = new (GetAllocator()) HGoto();
  non_escape->AddInstruction(write_non_escape);
  non_escape->AddInstruction(non_escape_goto);

  crit_break->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* bottom_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(bottom_call);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);
  bottom_call->CopyEnvironmentFrom(cls->GetEnvironment());

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_RETAINED(call_left);
  std::vector<HPhi*> merges;
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  std::tie(merges) = FindAllInstructions<HPhi>(graph_);
  ASSERT_EQ(merges.size(), 3u);
  HPhi* merge_value_return = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() == breturn;
  });
  HPhi* merge_value_merge = FindOrNull(merges.begin(), merges.end(), [&](HPhi* p) {
    return p->GetType() == DataType::Type::kInt32 && p->GetBlock() != breturn;
  });
  HPhi* merge_alloc = FindOrNull(merges.begin(), merges.end(), [](HPhi* p) {
    return p->GetType() == DataType::Type::kReference;
  });
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << merge_alloc->DumpWithArgs();
  EXPECT_INS_EQ(merge_alloc->InputAt(0)->InputAt(0), cls)
      << " phi is: " << merge_alloc->DumpWithArgs();
  EXPECT_INS_EQ(merge_alloc->InputAt(1), null_const);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), merge_value_return)
      << "get is " << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(merge_value_return->InputAt(0), merge_value_merge)
      << " phi is: " << *merge_value_return;
  EXPECT_INS_EQ(merge_value_return->InputAt(1), c1) << " phi is: " << *merge_value_return;
  EXPECT_INS_EQ(merge_value_merge->InputAt(0), graph_->GetIntConstant(0))
      << " phi is: " << *merge_value_merge;
  EXPECT_INS_EQ(merge_value_merge->InputAt(1), c2) << " phi is: " << *merge_value_merge;
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   // LEFT
//   obj.field = 3;
//   escape(obj);
// } else {
//   // RIGHT - Leave it as default value
// }
// EXIT
// predicated-ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PredicatedLoadDefaultValue) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});
  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* null_const = graph_->GetNullConstant();
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_left = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(write_left);
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(read_bottom);
  EXPECT_INS_RETAINED(write_left);
  EXPECT_INS_RETAINED(call_left);
  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  HPhi* merge_alloc = FindSingleInstruction<HPhi>(graph_, breturn);
  ASSERT_NE(merge_alloc, nullptr);
  EXPECT_TRUE(merge_alloc->InputAt(0)->IsNewInstance()) << *merge_alloc;
  EXPECT_EQ(merge_alloc->InputAt(0)->InputAt(0), cls) << *merge_alloc << " cls? " << *cls;
  EXPECT_EQ(merge_alloc->InputAt(1), null_const);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetTarget(), merge_alloc);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), c0) << " pred-get is: " << *pred_get;
}

// // ENTRY
// obj = new Obj();
// // ALL should be kept
// switch (parameter_value) {
//   case 1:
//     // Case1
//     obj.field = 1;
//     call_func(obj);
//     break;
//   case 2:
//     // Case2
//     obj.field = 2;
//     call_func(obj);
//     break;
//   default:
//     // Case3
//     obj.field = 3;
//     do {
//       if (test2()) { } else { obj.field = 5; }
//     } while (test());
//     break;
// }
// EXIT
// // predicated-ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoopPhis1) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "bswitch"},
                                                  {"bswitch", "case1"},
                                                  {"bswitch", "case2"},
                                                  {"bswitch", "case3"},
                                                  {"case1", "breturn"},
                                                  {"case2", "breturn"},
                                                  {"case3", "loop_pre_header"},
                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_merge"},
                                                  {"loop_if_right", "loop_merge"},
                                                  {"loop_merge", "loop_end"},
                                                  {"loop_end", "loop_header"},
                                                  {"loop_end", "critical_break"},
                                                  {"critical_break", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(bswitch);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(case1);
  GET_BLOCK(case2);
  GET_BLOCK(case3);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
  GET_BLOCK(loop_merge);
  GET_BLOCK(loop_end);
  GET_BLOCK(critical_break);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {case1, case2, critical_break});
  EnsurePredecessorOrder(loop_header, {loop_pre_header, loop_end});
  EnsurePredecessorOrder(loop_merge, {loop_if_left, loop_if_right});
  CHECK_SUBROUTINE_FAILURE();
  HInstruction* switch_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, switch_val);
  bswitch->AddInstruction(switch_inst);

  HInstruction* write_c1 = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_c1 = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_c1 = new (GetAllocator()) HGoto();
  case1->AddInstruction(write_c1);
  case1->AddInstruction(call_c1);
  case1->AddInstruction(goto_c1);
  call_c1->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_c2 = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_c2 = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_c2 = new (GetAllocator()) HGoto();
  case2->AddInstruction(write_c2);
  case2->AddInstruction(call_c2);
  case2->AddInstruction(goto_c2);
  call_c2->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_c3 = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_c3 = new (GetAllocator()) HGoto();
  case3->AddInstruction(write_c3);
  case3->AddInstruction(goto_c3);

  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* goto_header = new (GetAllocator()) HGoto();
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(goto_header);
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(goto_loop_left);

  HInstruction* write_loop_right = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);

  HInstruction* goto_loop_merge = new (GetAllocator()) HGoto();
  loop_merge->AddInstruction(goto_loop_merge);

  HInstruction* call_end = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_end = new (GetAllocator()) HIf(call_end);
  loop_end->AddInstruction(call_end);
  loop_end->AddInstruction(if_end);
  call_end->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_critical_break = new (GetAllocator()) HGoto();
  critical_break->AddInstruction(goto_critical_break);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_INS_REMOVED(read_bottom) << *read_bottom;
  ASSERT_TRUE(pred_get != nullptr);
  HPhi* inst_return_phi = pred_get->GetTarget()->AsPhi();
  ASSERT_TRUE(inst_return_phi != nullptr) << pred_get->GetTarget()->DumpWithArgs();
  EXPECT_INS_EQ(inst_return_phi->InputAt(0),
                FindSingleInstruction<HNewInstance>(graph_, case1->GetSinglePredecessor()));
  EXPECT_INS_EQ(inst_return_phi->InputAt(1),
                FindSingleInstruction<HNewInstance>(graph_, case2->GetSinglePredecessor()));
  EXPECT_INS_EQ(inst_return_phi->InputAt(2), graph_->GetNullConstant());
  HPhi* inst_value_phi = pred_get->GetDefaultValue()->AsPhi();
  ASSERT_TRUE(inst_value_phi != nullptr) << pred_get->GetDefaultValue()->DumpWithArgs();
  EXPECT_INS_EQ(inst_value_phi->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(inst_value_phi->InputAt(1), graph_->GetIntConstant(0));
  HPhi* loop_merge_phi = FindSingleInstruction<HPhi>(graph_, loop_merge);
  ASSERT_TRUE(loop_merge_phi != nullptr);
  HPhi* loop_header_phi = FindSingleInstruction<HPhi>(graph_, loop_header);
  ASSERT_TRUE(loop_header_phi != nullptr);
  EXPECT_INS_EQ(loop_header_phi->InputAt(0), c3);
  EXPECT_INS_EQ(loop_header_phi->InputAt(1), loop_merge_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(1), c5);
  EXPECT_INS_EQ(inst_value_phi->InputAt(2), loop_merge_phi);
  EXPECT_INS_RETAINED(write_c1) << *write_c1;
  EXPECT_INS_RETAINED(write_c2) << *write_c2;
  EXPECT_INS_REMOVED(write_c3) << *write_c3;
  EXPECT_INS_REMOVED(write_loop_right) << *write_loop_right;
}

// // ENTRY
// obj = new Obj();
// switch (parameter_value) {
//   case 1:
//     // Case1
//     obj.field = 1;
//     call_func(obj);
//     break;
//   case 2:
//     // Case2
//     obj.field = 2;
//     call_func(obj);
//     break;
//   default:
//     // Case3
//     obj.field = 3;
//     while (!test()) {
//       if (test2()) { } else { obj.field = 5; }
//     }
//     break;
// }
// EXIT
// // predicated-ELIMINATE
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoopPhis2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "bswitch"},
                                                  {"bswitch", "case1"},
                                                  {"bswitch", "case2"},
                                                  {"bswitch", "case3"},
                                                  {"case1", "breturn"},
                                                  {"case2", "breturn"},
                                                  {"case3", "loop_pre_header"},

                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "critical_break"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_merge"},
                                                  {"loop_if_right", "loop_merge"},
                                                  {"loop_merge", "loop_header"},

                                                  {"critical_break", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(bswitch);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(case1);
  GET_BLOCK(case2);
  GET_BLOCK(case3);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
  GET_BLOCK(loop_merge);
  GET_BLOCK(critical_break);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {case1, case2, critical_break});
  EnsurePredecessorOrder(loop_header, {loop_pre_header, loop_merge});
  EnsurePredecessorOrder(loop_merge, {loop_if_left, loop_if_right});
  CHECK_SUBROUTINE_FAILURE();
  HInstruction* switch_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c1 = graph_->GetIntConstant(1);
  HInstruction* c2 = graph_->GetIntConstant(2);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, switch_val);
  bswitch->AddInstruction(switch_inst);

  HInstruction* write_c1 = MakeIFieldSet(new_inst, c1, MemberOffset(32));
  HInstruction* call_c1 = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_c1 = new (GetAllocator()) HGoto();
  case1->AddInstruction(write_c1);
  case1->AddInstruction(call_c1);
  case1->AddInstruction(goto_c1);
  call_c1->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_c2 = MakeIFieldSet(new_inst, c2, MemberOffset(32));
  HInstruction* call_c2 = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_c2 = new (GetAllocator()) HGoto();
  case2->AddInstruction(write_c2);
  case2->AddInstruction(call_c2);
  case2->AddInstruction(goto_c2);
  call_c2->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_c3 = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_c3 = new (GetAllocator()) HGoto();
  case3->AddInstruction(write_c3);
  case3->AddInstruction(goto_c3);

  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_header = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_header = new (GetAllocator()) HIf(call_header);
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(call_header);
  loop_header->AddInstruction(if_header);
  call_header->CopyEnvironmentFrom(cls->GetEnvironment());
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(goto_loop_left);

  HInstruction* write_loop_right = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);

  HInstruction* goto_loop_merge = new (GetAllocator()) HGoto();
  loop_merge->AddInstruction(goto_loop_merge);

  HInstruction* goto_critical_break = new (GetAllocator()) HGoto();
  critical_break->AddInstruction(goto_critical_break);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_INS_REMOVED(read_bottom) << *read_bottom;
  ASSERT_TRUE(pred_get != nullptr);
  HPhi* inst_return_phi = pred_get->GetTarget()->AsPhi();
  ASSERT_TRUE(inst_return_phi != nullptr) << pred_get->GetTarget()->DumpWithArgs();
  EXPECT_INS_EQ(inst_return_phi->InputAt(0),
                FindSingleInstruction<HNewInstance>(graph_, case1->GetSinglePredecessor()));
  EXPECT_INS_EQ(inst_return_phi->InputAt(1),
                FindSingleInstruction<HNewInstance>(graph_, case2->GetSinglePredecessor()));
  EXPECT_INS_EQ(inst_return_phi->InputAt(2), graph_->GetNullConstant());
  HPhi* inst_value_phi = pred_get->GetDefaultValue()->AsPhi();
  ASSERT_TRUE(inst_value_phi != nullptr) << pred_get->GetDefaultValue()->DumpWithArgs();
  EXPECT_INS_EQ(inst_value_phi->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(inst_value_phi->InputAt(1), graph_->GetIntConstant(0));
  HPhi* loop_merge_phi = FindSingleInstruction<HPhi>(graph_, loop_merge);
  ASSERT_TRUE(loop_merge_phi != nullptr);
  HPhi* loop_header_phi = FindSingleInstruction<HPhi>(graph_, loop_header);
  ASSERT_TRUE(loop_header_phi != nullptr);
  EXPECT_INS_EQ(loop_header_phi->InputAt(0), c3);
  EXPECT_INS_EQ(loop_header_phi->InputAt(1), loop_merge_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(1), c5);
  EXPECT_INS_EQ(inst_value_phi->InputAt(2), loop_header_phi);
  EXPECT_INS_RETAINED(write_c1) << *write_c1;
  EXPECT_INS_RETAINED(write_c2) << *write_c2;
  EXPECT_INS_REMOVED(write_c3) << *write_c3;
  EXPECT_INS_REMOVED(write_loop_right) << *write_loop_right;
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// while (!test()) {
//   if (test2()) { } else { obj.field = 5; }
// }
// if (parameter_value) {
//   escape(obj);
// }
// EXIT
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoopPhis3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "loop_pre_header"},

                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "escape_check"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_merge"},
                                                  {"loop_if_right", "loop_merge"},
                                                  {"loop_merge", "loop_header"},

                                                  {"escape_check", "escape"},
                                                  {"escape_check", "no_escape"},
                                                  {"no_escape", "breturn"},
                                                  {"escape", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(no_escape);
  GET_BLOCK(escape);
  GET_BLOCK(escape_check);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
  GET_BLOCK(loop_merge);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {no_escape, escape});
  EnsurePredecessorOrder(loop_header, {loop_pre_header, loop_merge});
  EnsurePredecessorOrder(loop_merge, {loop_if_left, loop_if_right});
  CHECK_SUBROUTINE_FAILURE();
  HInstruction* bool_val = MakeParam(DataType::Type::kBool);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_pre_header = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(write_pre_header);
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_header = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_header = new (GetAllocator()) HIf(call_header);
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(call_header);
  loop_header->AddInstruction(if_header);
  call_header->CopyEnvironmentFrom(cls->GetEnvironment());
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(goto_loop_left);

  HInstruction* write_loop_right = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);

  HInstruction* goto_loop_merge = new (GetAllocator()) HGoto();
  loop_merge->AddInstruction(goto_loop_merge);

  HInstruction* if_esc_check = new (GetAllocator()) HIf(bool_val);
  escape_check->AddInstruction(if_esc_check);

  HInstruction* call_escape = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_escape = new (GetAllocator()) HGoto();
  escape->AddInstruction(call_escape);
  escape->AddInstruction(goto_escape);
  call_escape->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_no_escape = new (GetAllocator()) HGoto();
  no_escape->AddInstruction(goto_no_escape);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_INS_REMOVED(read_bottom) << *read_bottom;
  ASSERT_TRUE(pred_get != nullptr);
  HPhi* inst_return_phi = pred_get->GetTarget()->AsPhi();
  ASSERT_TRUE(inst_return_phi != nullptr) << pred_get->GetTarget()->DumpWithArgs();
  EXPECT_INS_EQ(inst_return_phi->InputAt(0), graph_->GetNullConstant());
  EXPECT_INS_EQ(inst_return_phi->InputAt(1),
                FindSingleInstruction<HNewInstance>(graph_, escape->GetSinglePredecessor()));
  HPhi* inst_value_phi = pred_get->GetDefaultValue()->AsPhi();
  ASSERT_TRUE(inst_value_phi != nullptr) << pred_get->GetDefaultValue()->DumpWithArgs();
  HPhi* loop_header_phi = FindSingleInstruction<HPhi>(graph_, loop_header);
  HPhi* loop_merge_phi = FindSingleInstruction<HPhi>(graph_, loop_merge);
  EXPECT_INS_EQ(inst_value_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(inst_value_phi->InputAt(1), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(loop_header_phi->InputAt(0), c3);
  EXPECT_INS_EQ(loop_header_phi->InputAt(1), loop_merge_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(1), c5);
  HInstanceFieldSet* mat_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, escape->GetSinglePredecessor());
  ASSERT_NE(mat_set, nullptr);
  EXPECT_INS_EQ(mat_set->InputAt(1), loop_header_phi);
  EXPECT_INS_REMOVED(write_loop_right) << *write_loop_right;
  EXPECT_INS_REMOVED(write_pre_header) << *write_pre_header;
}

// // ENTRY
// obj = new Obj();
// if (parameter_value) {
//   escape(obj);
// }
// obj.field = 3;
// while (!test()) {
//   if (test2()) { } else { obj.field = 5; }
// }
// EXIT
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoopPhis4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "escape_check"},
                                                  {"escape_check", "escape"},
                                                  {"escape_check", "no_escape"},
                                                  {"no_escape", "loop_pre_header"},
                                                  {"escape", "loop_pre_header"},

                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "breturn"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_merge"},
                                                  {"loop_if_right", "loop_merge"},
                                                  {"loop_merge", "loop_header"},

                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(no_escape);
  GET_BLOCK(escape);
  GET_BLOCK(escape_check);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
  GET_BLOCK(loop_merge);
#undef GET_BLOCK
  EnsurePredecessorOrder(loop_pre_header, {no_escape, escape});
  EnsurePredecessorOrder(loop_header, {loop_pre_header, loop_merge});
  EnsurePredecessorOrder(loop_merge, {loop_if_left, loop_if_right});
  CHECK_SUBROUTINE_FAILURE();
  HInstruction* bool_val = MakeParam(DataType::Type::kBool);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* if_esc_check = new (GetAllocator()) HIf(bool_val);
  escape_check->AddInstruction(if_esc_check);

  HInstruction* call_escape = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_escape = new (GetAllocator()) HGoto();
  escape->AddInstruction(call_escape);
  escape->AddInstruction(goto_escape);
  call_escape->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_no_escape = new (GetAllocator()) HGoto();
  no_escape->AddInstruction(goto_no_escape);

  HInstruction* write_pre_header = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(write_pre_header);
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_header = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_header = new (GetAllocator()) HIf(call_header);
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(call_header);
  loop_header->AddInstruction(if_header);
  call_header->CopyEnvironmentFrom(cls->GetEnvironment());
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(goto_loop_left);

  HInstruction* write_loop_right = MakeIFieldSet(new_inst, c5, MemberOffset(32));
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);

  HInstruction* goto_loop_merge = new (GetAllocator()) HGoto();
  loop_merge->AddInstruction(goto_loop_merge);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_INS_REMOVED(read_bottom) << *read_bottom;
  ASSERT_TRUE(pred_get != nullptr);
  HPhi* inst_return_phi = pred_get->GetTarget()->AsPhi();
  ASSERT_TRUE(inst_return_phi != nullptr) << pred_get->GetTarget()->DumpWithArgs();
  EXPECT_INS_EQ(inst_return_phi->InputAt(0), graph_->GetNullConstant());
  EXPECT_INS_EQ(inst_return_phi->InputAt(1),
                FindSingleInstruction<HNewInstance>(graph_, escape->GetSinglePredecessor()));
  HPhi* inst_value_phi = pred_get->GetDefaultValue()->AsPhi();
  ASSERT_TRUE(inst_value_phi != nullptr) << pred_get->GetDefaultValue()->DumpWithArgs();
  HPhi* loop_header_phi = FindSingleInstruction<HPhi>(graph_, loop_header);
  HPhi* loop_merge_phi = FindSingleInstruction<HPhi>(graph_, loop_merge);
  EXPECT_INS_EQ(inst_value_phi, loop_header_phi);
  EXPECT_INS_EQ(loop_header_phi->InputAt(0), c3);
  EXPECT_INS_EQ(loop_header_phi->InputAt(1), loop_merge_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(1), c5);
  EXPECT_INS_RETAINED(write_loop_right) << *write_loop_right;
  EXPECT_TRUE(write_loop_right->AsInstanceFieldSet()->GetIsPredicatedSet()) << *write_loop_right;
  EXPECT_INS_RETAINED(write_pre_header) << *write_pre_header;
  EXPECT_TRUE(write_pre_header->AsInstanceFieldSet()->GetIsPredicatedSet()) << *write_pre_header;
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// while (!test()) {
//   if (test2()) { } else { obj.field += 5; }
// }
// if (parameter_value) {
//   escape(obj);
// }
// EXIT
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoopPhis5) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "loop_pre_header"},
                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "escape_check"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_merge"},
                                                  {"loop_if_right", "loop_merge"},
                                                  {"loop_merge", "loop_header"},
                                                  {"escape_check", "escape"},
                                                  {"escape_check", "no_escape"},
                                                  {"no_escape", "breturn"},
                                                  {"escape", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(no_escape);
  GET_BLOCK(escape);
  GET_BLOCK(escape_check);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
  GET_BLOCK(loop_merge);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {no_escape, escape});
  EnsurePredecessorOrder(loop_header, {loop_pre_header, loop_merge});
  EnsurePredecessorOrder(loop_merge, {loop_if_left, loop_if_right});
  CHECK_SUBROUTINE_FAILURE();
  HInstruction* bool_val = MakeParam(DataType::Type::kBool);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_pre_header = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(write_pre_header);
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_header = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_header = new (GetAllocator()) HIf(call_header);
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(call_header);
  loop_header->AddInstruction(if_header);
  call_header->CopyEnvironmentFrom(cls->GetEnvironment());
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(goto_loop_left);

  HInstruction* read_loop_right = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_loop_right =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, read_loop_right, c5);
  HInstruction* write_loop_right = MakeIFieldSet(new_inst, add_loop_right, MemberOffset(32));
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(read_loop_right);
  loop_if_right->AddInstruction(add_loop_right);
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);

  HInstruction* goto_loop_merge = new (GetAllocator()) HGoto();
  loop_merge->AddInstruction(goto_loop_merge);

  HInstruction* if_esc_check = new (GetAllocator()) HIf(bool_val);
  escape_check->AddInstruction(if_esc_check);

  HInstruction* call_escape = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_escape = new (GetAllocator()) HGoto();
  escape->AddInstruction(call_escape);
  escape->AddInstruction(goto_escape);
  call_escape->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_no_escape = new (GetAllocator()) HGoto();
  no_escape->AddInstruction(goto_no_escape);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_INS_REMOVED(read_bottom) << *read_bottom;
  ASSERT_TRUE(pred_get != nullptr);
  HPhi* inst_return_phi = pred_get->GetTarget()->AsPhi();
  ASSERT_TRUE(inst_return_phi != nullptr) << pred_get->GetTarget()->DumpWithArgs();
  EXPECT_INS_EQ(inst_return_phi->InputAt(0), graph_->GetNullConstant());
  EXPECT_INS_EQ(inst_return_phi->InputAt(1),
                FindSingleInstruction<HNewInstance>(graph_, escape->GetSinglePredecessor()));
  HPhi* inst_value_phi = pred_get->GetDefaultValue()->AsPhi();
  ASSERT_TRUE(inst_value_phi != nullptr) << pred_get->GetDefaultValue()->DumpWithArgs();
  HPhi* loop_header_phi = FindSingleInstruction<HPhi>(graph_, loop_header);
  HPhi* loop_merge_phi = FindSingleInstruction<HPhi>(graph_, loop_merge);
  EXPECT_INS_EQ(inst_value_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(inst_value_phi->InputAt(1), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(loop_header_phi->InputAt(0), c3);
  EXPECT_INS_EQ(loop_header_phi->InputAt(1), loop_merge_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(loop_merge_phi->InputAt(1), add_loop_right);
  EXPECT_INS_EQ(add_loop_right->InputAt(0), loop_header_phi);
  EXPECT_INS_EQ(add_loop_right->InputAt(1), c5);
  HInstanceFieldSet* mat_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, escape->GetSinglePredecessor());
  ASSERT_NE(mat_set, nullptr);
  EXPECT_INS_EQ(mat_set->InputAt(1), loop_header_phi);
  EXPECT_INS_REMOVED(write_loop_right) << *write_loop_right;
  EXPECT_INS_REMOVED(write_pre_header) << *write_pre_header;
}

// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (param) {
//   while (!test()) {
//     if (test2()) {
//       noescape();
//     } else {
//       abc = obj.field;
//       obj.field = abc + 5;
//       noescape();
//     }
//   }
//   escape(obj);
// } else {
// }
// return obj.field
TEST_F(LoadStoreEliminationTest, PartialLoopPhis6) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(/*handles=*/&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "start"},
                                                  {"start", "left"},
                                                  {"start", "right"},
                                                  {"left", "loop_pre_header"},

                                                  {"loop_pre_header", "loop_header"},
                                                  {"loop_header", "escape"},
                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_if_left"},
                                                  {"loop_body", "loop_if_right"},
                                                  {"loop_if_left", "loop_header"},
                                                  {"loop_if_right", "loop_header"},

                                                  {"escape", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(start);
  GET_BLOCK(escape);

  GET_BLOCK(loop_pre_header);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_if_left);
  GET_BLOCK(loop_if_right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {escape, right});
  EnsurePredecessorOrder(loop_header, {loop_pre_header, loop_if_left, loop_if_right});
  CHECK_SUBROUTINE_FAILURE();
  HInstruction* bool_val = MakeParam(DataType::Type::kBool);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c5 = graph_->GetIntConstant(5);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_entry = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* entry_goto = new (GetAllocator()) HGoto();
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_entry);
  entry->AddInstruction(entry_goto);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  start->AddInstruction(new (GetAllocator()) HIf(bool_val));

  HInstruction* left_goto = new (GetAllocator()) HGoto();
  left->AddInstruction(left_goto);

  HInstruction* goto_preheader = new (GetAllocator()) HGoto();
  loop_pre_header->AddInstruction(goto_preheader);

  HInstruction* suspend_check_header = new (GetAllocator()) HSuspendCheck();
  HInstruction* call_header = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_header = new (GetAllocator()) HIf(call_header);
  loop_header->AddInstruction(suspend_check_header);
  loop_header->AddInstruction(call_header);
  loop_header->AddInstruction(if_header);
  call_header->CopyEnvironmentFrom(cls->GetEnvironment());
  suspend_check_header->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_body = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* if_loop_body = new (GetAllocator()) HIf(call_loop_body);
  loop_body->AddInstruction(call_loop_body);
  loop_body->AddInstruction(if_loop_body);
  call_loop_body->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_loop_left = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* goto_loop_left = new (GetAllocator()) HGoto();
  loop_if_left->AddInstruction(call_loop_left);
  loop_if_left->AddInstruction(goto_loop_left);
  call_loop_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_loop_right = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* add_loop_right =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, c5, read_loop_right);
  HInstruction* write_loop_right = MakeIFieldSet(new_inst, add_loop_right, MemberOffset(32));
  HInstruction* call_loop_right = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* goto_loop_right = new (GetAllocator()) HGoto();
  loop_if_right->AddInstruction(read_loop_right);
  loop_if_right->AddInstruction(add_loop_right);
  loop_if_right->AddInstruction(write_loop_right);
  loop_if_right->AddInstruction(call_loop_right);
  loop_if_right->AddInstruction(goto_loop_right);
  call_loop_right->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_escape = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_escape = new (GetAllocator()) HGoto();
  escape->AddInstruction(call_escape);
  escape->AddInstruction(goto_escape);
  call_escape->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(goto_right);

  HInstruction* read_bottom = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_bottom);
  breturn->AddInstruction(read_bottom);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSEWithPartial();
  LOG(INFO) << "Post LSE " << blks;

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  EXPECT_INS_REMOVED(read_bottom) << *read_bottom;
  ASSERT_TRUE(pred_get != nullptr);
  HPhi* inst_return_phi = pred_get->GetTarget()->AsPhi();
  ASSERT_TRUE(inst_return_phi != nullptr) << pred_get->GetTarget()->DumpWithArgs();
  EXPECT_INS_EQ(inst_return_phi->InputAt(0),
                FindSingleInstruction<HNewInstance>(graph_, escape->GetSinglePredecessor()));
  EXPECT_INS_EQ(inst_return_phi->InputAt(1), graph_->GetNullConstant());
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(1), c3);
  HPhi* loop_header_phi = FindSingleInstruction<HPhi>(graph_, loop_header);
  ASSERT_NE(loop_header_phi, nullptr);
  EXPECT_INS_EQ(loop_header_phi->InputAt(0), c3);
  EXPECT_INS_EQ(loop_header_phi->InputAt(1), loop_header_phi);
  EXPECT_INS_EQ(loop_header_phi->InputAt(2), add_loop_right);
  EXPECT_INS_EQ(add_loop_right->InputAt(0), c5);
  EXPECT_INS_EQ(add_loop_right->InputAt(1), loop_header_phi);
  HInstanceFieldSet* mat_set =
      FindSingleInstruction<HInstanceFieldSet>(graph_, escape->GetSinglePredecessor());
  ASSERT_NE(mat_set, nullptr);
  EXPECT_INS_EQ(mat_set->InputAt(1), loop_header_phi);
  EXPECT_INS_REMOVED(write_loop_right);
  EXPECT_INS_REMOVED(write_entry);
  EXPECT_INS_RETAINED(call_header);
  EXPECT_INS_RETAINED(call_loop_left);
  EXPECT_INS_RETAINED(call_loop_right);
}

// TODO This should really be in an Instruction simplifier Gtest but (1) that
// doesn't exist and (2) we should move this simplification to directly in the
// LSE pass since there is more information then.
// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (param) {
//   escape(obj);
// } else {
//   obj.field = 10;
// }
// return obj.field;
TEST_F(LoadStoreEliminationTest, SimplifyTest) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});

  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c10 = graph_->GetIntConstant(10);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_start = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_start);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_right = MakeIFieldSet(new_inst, c10, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);

  HInstruction* read_end = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_end);
  breturn->AddInstruction(read_end);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();

  // Run the code-simplifier too
  LOG(INFO) << "Pre simplification " << blks;
  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
  simp.Run();

  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_REMOVED(write_start);
  EXPECT_INS_REMOVED(read_end);
  EXPECT_INS_RETAINED(call_left);

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), c10);
}


// TODO This should really be in an Instruction simplifier Gtest but (1) that
// doesn't exist and (2) we should move this simplification to directly in the
// LSE pass since there is more information then.
//
// This checks that we don't replace phis when the replacement isn't valid at
// that point (i.e. it doesn't dominate)
// // ENTRY
// obj = new Obj();
// obj.field = 3;
// if (param) {
//   escape(obj);
// } else {
//   obj.field = noescape();
// }
// return obj.field;
TEST_F(LoadStoreEliminationTest, SimplifyTest2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},
                                                  {"right", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, right});

  HInstruction* bool_value = MakeParam(DataType::Type::kBool);
  HInstruction* c3 = graph_->GetIntConstant(3);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_start = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(bool_value);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_start);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_left = MakeInvoke(DataType::Type::kVoid, {new_inst});
  HInstruction* goto_left = new (GetAllocator()) HGoto();
  left->AddInstruction(call_left);
  left->AddInstruction(goto_left);
  call_left->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_right = MakeInvoke(DataType::Type::kInt32, {});
  HInstruction* write_right = MakeIFieldSet(new_inst, call_right, MemberOffset(32));
  HInstruction* goto_right = new (GetAllocator()) HGoto();
  right->AddInstruction(call_right);
  right->AddInstruction(write_right);
  right->AddInstruction(goto_right);
  call_right->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* read_end = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_end);
  breturn->AddInstruction(read_end);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();

  // Run the code-simplifier too
  LOG(INFO) << "Pre simplification " << blks;
  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
  simp.Run();

  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(write_right);
  EXPECT_INS_REMOVED(write_start);
  EXPECT_INS_REMOVED(read_end);
  EXPECT_INS_RETAINED(call_left);
  EXPECT_INS_RETAINED(call_right);
  EXPECT_EQ(call_right->GetBlock(), right);

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_TRUE(pred_get->GetDefaultValue()->IsPhi()) << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(0), graph_->GetIntConstant(0))
      << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(1), call_right) << pred_get->DumpWithArgs();
}

// TODO This should really be in an Instruction simplifier Gtest but (1) that
// doesn't exist and (2) we should move this simplification to directly in the
// LSE pass since there is more information then.
//
// This checks that we replace phis even when there are multiple replacements as
// long as they are equal
// // ENTRY
// obj = new Obj();
// obj.field = 3;
// switch (param) {
//   case 1:
//     escape(obj);
//     break;
//   case 2:
//     obj.field = 10;
//     break;
//   case 3:
//     obj.field = 10;
//     break;
// }
// return obj.field;
TEST_F(LoadStoreEliminationTest, SimplifyTest3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "case1"},
                                                  {"entry", "case2"},
                                                  {"entry", "case3"},
                                                  {"case1", "breturn"},
                                                  {"case2", "breturn"},
                                                  {"case3", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(case1);
  GET_BLOCK(case2);
  GET_BLOCK(case3);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {case1, case2, case3});

  HInstruction* int_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c10 = graph_->GetIntConstant(10);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_start = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, int_val);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_start);
  entry->AddInstruction(switch_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_case1 = MakeInvoke(DataType::Type::kVoid, {new_inst});
  HInstruction* goto_case1 = new (GetAllocator()) HGoto();
  case1->AddInstruction(call_case1);
  case1->AddInstruction(goto_case1);
  call_case1->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_case2 = MakeIFieldSet(new_inst, c10, MemberOffset(32));
  HInstruction* goto_case2 = new (GetAllocator()) HGoto();
  case2->AddInstruction(write_case2);
  case2->AddInstruction(goto_case2);

  HInstruction* write_case3 = MakeIFieldSet(new_inst, c10, MemberOffset(32));
  HInstruction* goto_case3 = new (GetAllocator()) HGoto();
  case3->AddInstruction(write_case3);
  case3->AddInstruction(goto_case3);

  HInstruction* read_end = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_end);
  breturn->AddInstruction(read_end);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();

  // Run the code-simplifier too
  LOG(INFO) << "Pre simplification " << blks;
  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
  simp.Run();

  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(write_case2);
  EXPECT_INS_REMOVED(write_case3);
  EXPECT_INS_REMOVED(write_start);
  EXPECT_INS_REMOVED(read_end);
  EXPECT_INS_RETAINED(call_case1);

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_INS_EQ(pred_get->GetDefaultValue(), c10)
      << pred_get->DumpWithArgs();
}

// TODO This should really be in an Instruction simplifier Gtest but (1) that
// doesn't exist and (2) we should move this simplification to directly in the
// LSE pass since there is more information then.
//
// This checks that we don't replace phis even when there are multiple
// replacements if they are not equal
// // ENTRY
// obj = new Obj();
// obj.field = 3;
// switch (param) {
//   case 1:
//     escape(obj);
//     break;
//   case 2:
//     obj.field = 10;
//     break;
//   case 3:
//     obj.field = 20;
//     break;
// }
// return obj.field;
TEST_F(LoadStoreEliminationTest, SimplifyTest4) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "case1"},
                                                  {"entry", "case2"},
                                                  {"entry", "case3"},
                                                  {"case1", "breturn"},
                                                  {"case2", "breturn"},
                                                  {"case3", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(case1);
  GET_BLOCK(case2);
  GET_BLOCK(case3);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {case1, case2, case3});

  HInstruction* int_val = MakeParam(DataType::Type::kInt32);
  HInstruction* c3 = graph_->GetIntConstant(3);
  HInstruction* c10 = graph_->GetIntConstant(10);
  HInstruction* c20 = graph_->GetIntConstant(20);

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_start = MakeIFieldSet(new_inst, c3, MemberOffset(32));
  HInstruction* switch_inst = new (GetAllocator()) HPackedSwitch(0, 2, int_val);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_start);
  entry->AddInstruction(switch_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* call_case1 = MakeInvoke(DataType::Type::kVoid, {new_inst});
  HInstruction* goto_case1 = new (GetAllocator()) HGoto();
  case1->AddInstruction(call_case1);
  case1->AddInstruction(goto_case1);
  call_case1->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* write_case2 = MakeIFieldSet(new_inst, c10, MemberOffset(32));
  HInstruction* goto_case2 = new (GetAllocator()) HGoto();
  case2->AddInstruction(write_case2);
  case2->AddInstruction(goto_case2);

  HInstruction* write_case3 = MakeIFieldSet(new_inst, c20, MemberOffset(32));
  HInstruction* goto_case3 = new (GetAllocator()) HGoto();
  case3->AddInstruction(write_case3);
  case3->AddInstruction(goto_case3);

  HInstruction* read_end = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_end);
  breturn->AddInstruction(read_end);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();

  // Run the code-simplifier too
  LOG(INFO) << "Pre simplification " << blks;
  InstructionSimplifier simp(graph_, /*codegen=*/nullptr);
  simp.Run();

  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_REMOVED(write_case2);
  EXPECT_INS_REMOVED(write_case3);
  EXPECT_INS_REMOVED(write_start);
  EXPECT_INS_REMOVED(read_end);
  EXPECT_INS_RETAINED(call_case1);

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(pred_get, nullptr);
  EXPECT_TRUE(pred_get->GetDefaultValue()->IsPhi())
      << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(0), graph_->GetIntConstant(0));
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(1), c10);
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(2), c20);
}

// Make sure that irreducible loops don't screw up Partial LSE. We can't pull
// phis through them so we need to treat them as escapes.
// TODO We should be able to do better than this? Need to do some research.
// // ENTRY
// obj = new Obj();
// obj.foo = 11;
// if (param1) {
// } else {
//   // irreducible loop here. NB the objdoesn't actually escape
//   obj.foo = 33;
//   if (param2) {
//     goto inner;
//   } else {
//     while (test()) {
//       if (test()) {
//         obj.foo = 66;
//       } else {
//       }
//       inner:
//     }
//   }
// }
// return obj.foo;
TEST_F(LoadStoreEliminationTest, PartialIrreducibleLoop) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("start",
                                                 "exit",
                                                 {{"start", "entry"},
                                                  {"entry", "left"},
                                                  {"entry", "right"},
                                                  {"left", "breturn"},

                                                  {"right", "right_crit_break_loop"},
                                                  {"right_crit_break_loop", "loop_header"},
                                                  {"right", "right_crit_break_end"},
                                                  {"right_crit_break_end", "loop_end"},

                                                  {"loop_header", "loop_body"},
                                                  {"loop_body", "loop_left"},
                                                  {"loop_body", "loop_right"},
                                                  {"loop_left", "loop_end"},
                                                  {"loop_right", "loop_end"},
                                                  {"loop_end", "loop_header"},
                                                  {"loop_header", "loop_header_crit_break"},
                                                  {"loop_header_crit_break", "breturn"},

                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(start);
  GET_BLOCK(entry);
  GET_BLOCK(exit);
  GET_BLOCK(breturn);
  GET_BLOCK(left);
  GET_BLOCK(right);
  GET_BLOCK(right_crit_break_end);
  GET_BLOCK(right_crit_break_loop);
  GET_BLOCK(loop_header);
  GET_BLOCK(loop_header_crit_break);
  GET_BLOCK(loop_body);
  GET_BLOCK(loop_left);
  GET_BLOCK(loop_right);
  GET_BLOCK(loop_end);
#undef GET_BLOCK
  EnsurePredecessorOrder(breturn, {left, loop_header_crit_break});
  HInstruction* c11 = graph_->GetIntConstant(11);
  HInstruction* c33 = graph_->GetIntConstant(33);
  HInstruction* c66 = graph_->GetIntConstant(66);
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);

  HInstruction* suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* start_goto = new (GetAllocator()) HGoto();
  start->AddInstruction(suspend);
  start->AddInstruction(start_goto);
  ManuallyBuildEnvFor(suspend, {});

  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* write_start = MakeIFieldSet(new_inst, c11, MemberOffset(32));
  HInstruction* if_inst = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(write_start);
  entry->AddInstruction(if_inst);
  ManuallyBuildEnvFor(cls, {});
  new_inst->CopyEnvironmentFrom(cls->GetEnvironment());

  left->AddInstruction(new (GetAllocator()) HGoto());

  right->AddInstruction(MakeIFieldSet(new_inst, c33, MemberOffset(32)));
  right->AddInstruction(new (GetAllocator()) HIf(param2));

  right_crit_break_end->AddInstruction(new (GetAllocator()) HGoto());
  right_crit_break_loop->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* header_suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* header_invoke = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* header_if = new (GetAllocator()) HIf(header_invoke);
  loop_header->AddInstruction(header_suspend);
  loop_header->AddInstruction(header_invoke);
  loop_header->AddInstruction(header_if);
  header_suspend->CopyEnvironmentFrom(cls->GetEnvironment());
  header_invoke->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* body_invoke = MakeInvoke(DataType::Type::kBool, {});
  HInstruction* body_if = new (GetAllocator()) HIf(body_invoke);
  loop_body->AddInstruction(body_invoke);
  loop_body->AddInstruction(body_if);
  body_invoke->CopyEnvironmentFrom(cls->GetEnvironment());

  HInstruction* left_set = MakeIFieldSet(new_inst, c66, MemberOffset(32));
  HInstruction* left_goto = MakeIFieldSet(new_inst, c66, MemberOffset(32));
  loop_left->AddInstruction(left_set);
  loop_left->AddInstruction(left_goto);

  loop_right->AddInstruction(new (GetAllocator()) HGoto());

  loop_end->AddInstruction(new (GetAllocator()) HGoto());

  HInstruction* read_end = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* return_exit = new (GetAllocator()) HReturn(read_end);
  breturn->AddInstruction(read_end);
  breturn->AddInstruction(return_exit);

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_TRUE(loop_header->IsLoopHeader());
  EXPECT_TRUE(loop_header->GetLoopInformation()->IsIrreducible());

  EXPECT_INS_RETAINED(left_set);
  EXPECT_INS_REMOVED(write_start);
  EXPECT_INS_REMOVED(read_end);

  HPredicatedInstanceFieldGet* pred_get =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(pred_get, nullptr);
  ASSERT_TRUE(pred_get->GetDefaultValue()->IsPhi()) << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(0), c11);
  EXPECT_INS_EQ(pred_get->GetDefaultValue()->InputAt(1), graph_->GetIntConstant(0));
  ASSERT_TRUE(pred_get->GetTarget()->IsPhi()) << pred_get->DumpWithArgs();
  EXPECT_INS_EQ(pred_get->GetTarget()->InputAt(0), graph_->GetNullConstant());
  HNewInstance* mat = FindSingleInstruction<HNewInstance>(graph_, right->GetSinglePredecessor());
  ASSERT_NE(mat, nullptr);
  EXPECT_INS_EQ(pred_get->GetTarget()->InputAt(1), mat);
}

enum class UsesOrder { kDefaultOrder, kReverseOrder };
std::ostream& operator<<(std::ostream& os, const UsesOrder& ord) {
  switch (ord) {
    case UsesOrder::kDefaultOrder:
      return os << "DefaultOrder";
    case UsesOrder::kReverseOrder:
      return os << "ReverseOrder";
  }
}

class UsesOrderDependentTestGroup
    : public LoadStoreEliminationTestBase<CommonCompilerTestWithParam<UsesOrder>> {};

// Make sure that we record replacements by predicated loads and use them
// instead of constructing Phis with inputs removed from the graph. Bug: 183897743
// Note that the bug was hit only for a certain ordering of the NewInstance
// uses, so we test both orderings.
// // ENTRY
// obj = new Obj();
// obj.foo = 11;
// if (param1) {
//   // LEFT1
//   escape(obj);
// } else {
//   // RIGHT1
// }
// // MIDDLE
// a = obj.foo;
// if (param2) {
//   // LEFT2
//   obj.foo = 33;
// } else {
//   // RIGHT2
// }
// // BRETURN
// no_escape()  // If `obj` escaped, the field value can change. (Avoid non-partial LSE.)
// b = obj.foo;
// return a + b;
TEST_P(UsesOrderDependentTestGroup, RecordPredicatedReplacements1) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left1"},
                                                  {"entry", "right1"},
                                                  {"left1", "middle"},
                                                  {"right1", "middle"},
                                                  {"middle", "left2"},
                                                  {"middle", "right2"},
                                                  {"left2", "breturn"},
                                                  {"right2", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(left1);
  GET_BLOCK(right1);
  GET_BLOCK(middle);
  GET_BLOCK(left2);
  GET_BLOCK(right2);
  GET_BLOCK(breturn);
  GET_BLOCK(exit);
#undef GET_BLOCK
  EnsurePredecessorOrder(middle, {left1, right1});
  EnsurePredecessorOrder(breturn, {left2, right2});
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* cnull = graph_->GetNullConstant();
  HInstruction* c11 = graph_->GetIntConstant(11);
  HInstruction* c33 = graph_->GetIntConstant(33);
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);

  HInstruction* suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_write = MakeIFieldSet(new_inst, c11, MemberOffset(32));
  HInstruction* entry_if = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(suspend);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_write);
  entry->AddInstruction(entry_if);
  ManuallyBuildEnvFor(suspend, {});
  ManuallyBuildEnvFor(cls, {});
  ManuallyBuildEnvFor(new_inst, {});

  HInstruction* left1_call = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* left1_goto = new (GetAllocator()) HGoto();
  left1->AddInstruction(left1_call);
  left1->AddInstruction(left1_goto);
  ManuallyBuildEnvFor(left1_call, {});

  HInstruction* right1_goto = new (GetAllocator()) HGoto();
  right1->AddInstruction(right1_goto);

  HInstruction* middle_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* middle_if = new (GetAllocator()) HIf(param2);
  if (GetParam() == UsesOrder::kDefaultOrder) {
    middle->AddInstruction(middle_read);
  }
  middle->AddInstruction(middle_if);

  HInstanceFieldSet* left2_write = MakeIFieldSet(new_inst, c33, MemberOffset(32));
  HInstruction* left2_goto = new (GetAllocator()) HGoto();
  left2->AddInstruction(left2_write);
  left2->AddInstruction(left2_goto);

  HInstruction* right2_goto = new (GetAllocator()) HGoto();
  right2->AddInstruction(right2_goto);

  HInstruction* breturn_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* breturn_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* breturn_add =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, middle_read, breturn_read);
  HInstruction* breturn_return = new (GetAllocator()) HReturn(breturn_add);
  breturn->AddInstruction(breturn_call);
  breturn->AddInstruction(breturn_read);
  breturn->AddInstruction(breturn_add);
  breturn->AddInstruction(breturn_return);
  ManuallyBuildEnvFor(breturn_call, {});

  if (GetParam() == UsesOrder::kReverseOrder) {
    // Insert `middle_read` in the same position as for the `kDefaultOrder` case.
    // The only difference is the order of entries in `new_inst->GetUses()` which
    // is used by `HeapReferenceData::CollectReplacements()` and defines the order
    // of instructions to process for `HeapReferenceData::PredicateInstructions()`.
    middle->InsertInstructionBefore(middle_read, middle_if);
  }

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_REMOVED(new_inst);
  HNewInstance* replacement_new_inst = FindSingleInstruction<HNewInstance>(graph_);
  ASSERT_NE(replacement_new_inst, nullptr);
  EXPECT_INS_REMOVED(entry_write);
  std::vector<HInstanceFieldSet*> all_writes;
  std::tie(all_writes) = FindAllInstructions<HInstanceFieldSet>(graph_);
  ASSERT_EQ(2u, all_writes.size());
  ASSERT_NE(all_writes[0] == left2_write, all_writes[1] == left2_write);
  HInstanceFieldSet* replacement_write = all_writes[(all_writes[0] == left2_write) ? 1u : 0u];
  ASSERT_FALSE(replacement_write->GetIsPredicatedSet());
  ASSERT_INS_EQ(replacement_write->InputAt(0), replacement_new_inst);
  ASSERT_INS_EQ(replacement_write->InputAt(1), c11);

  EXPECT_INS_RETAINED(left1_call);

  EXPECT_INS_REMOVED(middle_read);
  HPredicatedInstanceFieldGet* replacement_middle_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, middle);
  ASSERT_NE(replacement_middle_read, nullptr);
  ASSERT_TRUE(replacement_middle_read->GetTarget()->IsPhi());
  ASSERT_EQ(2u, replacement_middle_read->GetTarget()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_middle_read->GetTarget()->AsPhi()->InputAt(0), replacement_new_inst);
  ASSERT_INS_EQ(replacement_middle_read->GetTarget()->AsPhi()->InputAt(1), cnull);
  ASSERT_TRUE(replacement_middle_read->GetDefaultValue()->IsPhi());
  ASSERT_EQ(2u, replacement_middle_read->GetDefaultValue()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_middle_read->GetDefaultValue()->AsPhi()->InputAt(0), c0);
  ASSERT_INS_EQ(replacement_middle_read->GetDefaultValue()->AsPhi()->InputAt(1), c11);

  EXPECT_INS_RETAINED(left2_write);
  ASSERT_TRUE(left2_write->GetIsPredicatedSet());

  EXPECT_INS_REMOVED(breturn_read);
  HPredicatedInstanceFieldGet* replacement_breturn_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(replacement_breturn_read, nullptr);
  ASSERT_INS_EQ(replacement_breturn_read->GetTarget(), replacement_middle_read->GetTarget());
  ASSERT_TRUE(replacement_breturn_read->GetDefaultValue()->IsPhi());
  ASSERT_EQ(2u, replacement_breturn_read->GetDefaultValue()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_breturn_read->GetDefaultValue()->AsPhi()->InputAt(0), c33);
  HInstruction* other_input = replacement_breturn_read->GetDefaultValue()->AsPhi()->InputAt(1);
  ASSERT_NE(other_input->GetBlock(), nullptr) << GetParam();
  ASSERT_INS_EQ(other_input, replacement_middle_read);
}

// Regression test for a bad DCHECK() found while trying to write a test for b/188188275.
// // ENTRY
// obj = new Obj();
// obj.foo = 11;
// if (param1) {
//   // LEFT1
//   escape(obj);
// } else {
//   // RIGHT1
// }
// // MIDDLE
// a = obj.foo;
// if (param2) {
//   // LEFT2
//   no_escape();
// } else {
//   // RIGHT2
// }
// // BRETURN
// b = obj.foo;
// return a + b;
TEST_P(UsesOrderDependentTestGroup, RecordPredicatedReplacements2) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left1"},
                                                  {"entry", "right1"},
                                                  {"left1", "middle"},
                                                  {"right1", "middle"},
                                                  {"middle", "left2"},
                                                  {"middle", "right2"},
                                                  {"left2", "breturn"},
                                                  {"right2", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(left1);
  GET_BLOCK(right1);
  GET_BLOCK(middle);
  GET_BLOCK(left2);
  GET_BLOCK(right2);
  GET_BLOCK(breturn);
  GET_BLOCK(exit);
#undef GET_BLOCK
  EnsurePredecessorOrder(middle, {left1, right1});
  EnsurePredecessorOrder(breturn, {left2, right2});
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* cnull = graph_->GetNullConstant();
  HInstruction* c11 = graph_->GetIntConstant(11);
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);

  HInstruction* suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_write = MakeIFieldSet(new_inst, c11, MemberOffset(32));
  HInstruction* entry_if = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(suspend);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_write);
  entry->AddInstruction(entry_if);
  ManuallyBuildEnvFor(suspend, {});
  ManuallyBuildEnvFor(cls, {});
  ManuallyBuildEnvFor(new_inst, {});

  HInstruction* left1_call = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* left1_goto = new (GetAllocator()) HGoto();
  left1->AddInstruction(left1_call);
  left1->AddInstruction(left1_goto);
  ManuallyBuildEnvFor(left1_call, {});

  HInstruction* right1_goto = new (GetAllocator()) HGoto();
  right1->AddInstruction(right1_goto);

  HInstruction* middle_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* middle_if = new (GetAllocator()) HIf(param2);
  if (GetParam() == UsesOrder::kDefaultOrder) {
    middle->AddInstruction(middle_read);
  }
  middle->AddInstruction(middle_if);

  HInstruction* left2_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* left2_goto = new (GetAllocator()) HGoto();
  left2->AddInstruction(left2_call);
  left2->AddInstruction(left2_goto);
  ManuallyBuildEnvFor(left2_call, {});

  HInstruction* right2_goto = new (GetAllocator()) HGoto();
  right2->AddInstruction(right2_goto);

  HInstruction* breturn_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* breturn_add =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, middle_read, breturn_read);
  HInstruction* breturn_return = new (GetAllocator()) HReturn(breturn_add);
  breturn->AddInstruction(breturn_read);
  breturn->AddInstruction(breturn_add);
  breturn->AddInstruction(breturn_return);

  if (GetParam() == UsesOrder::kReverseOrder) {
    // Insert `middle_read` in the same position as for the `kDefaultOrder` case.
    // The only difference is the order of entries in `new_inst->GetUses()` which
    // is used by `HeapReferenceData::CollectReplacements()` and defines the order
    // of instructions to process for `HeapReferenceData::PredicateInstructions()`.
    middle->InsertInstructionBefore(middle_read, middle_if);
  }

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_REMOVED(new_inst);
  HNewInstance* replacement_new_inst = FindSingleInstruction<HNewInstance>(graph_);
  ASSERT_NE(replacement_new_inst, nullptr);
  EXPECT_INS_REMOVED(entry_write);
  HInstanceFieldSet* replacement_write = FindSingleInstruction<HInstanceFieldSet>(graph_);
  ASSERT_NE(replacement_write, nullptr);
  ASSERT_FALSE(replacement_write->GetIsPredicatedSet());
  ASSERT_INS_EQ(replacement_write->InputAt(0), replacement_new_inst);
  ASSERT_INS_EQ(replacement_write->InputAt(1), c11);

  EXPECT_INS_RETAINED(left1_call);

  EXPECT_INS_REMOVED(middle_read);
  HPredicatedInstanceFieldGet* replacement_middle_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, middle);
  ASSERT_NE(replacement_middle_read, nullptr);
  ASSERT_TRUE(replacement_middle_read->GetTarget()->IsPhi());
  ASSERT_EQ(2u, replacement_middle_read->GetTarget()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_middle_read->GetTarget()->AsPhi()->InputAt(0), replacement_new_inst);
  ASSERT_INS_EQ(replacement_middle_read->GetTarget()->AsPhi()->InputAt(1), cnull);
  ASSERT_TRUE(replacement_middle_read->GetDefaultValue()->IsPhi());
  ASSERT_EQ(2u, replacement_middle_read->GetDefaultValue()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_middle_read->GetDefaultValue()->AsPhi()->InputAt(0), c0);
  ASSERT_INS_EQ(replacement_middle_read->GetDefaultValue()->AsPhi()->InputAt(1), c11);

  EXPECT_INS_RETAINED(left2_call);

  EXPECT_INS_REMOVED(breturn_read);
  HPredicatedInstanceFieldGet* replacement_breturn_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(replacement_breturn_read, nullptr);
  ASSERT_INS_EQ(replacement_breturn_read->GetTarget(), replacement_middle_read->GetTarget());
  ASSERT_INS_EQ(replacement_breturn_read->GetDefaultValue(), replacement_middle_read);
}

INSTANTIATE_TEST_SUITE_P(LoadStoreEliminationTest,
                         UsesOrderDependentTestGroup,
                         testing::Values(UsesOrder::kDefaultOrder, UsesOrder::kReverseOrder));

// The parameter is the number of times we call `std::next_permutation` (from 0 to 5)
// so that we test all 6 permutation of three items.
class UsesOrderDependentTestGroupForThreeItems
    : public LoadStoreEliminationTestBase<CommonCompilerTestWithParam<size_t>> {};

// Make sure that after we record replacements by predicated loads, we correctly
// use that predicated load for Phi placeholders that were previously marked as
// replaced by the now removed unpredicated load. (The fix for bug 183897743 was
// not good enough.) Bug: 188188275
// // ENTRY
// obj = new Obj();
// obj.foo = 11;
// if (param1) {
//   // LEFT1
//   escape(obj);
// } else {
//   // RIGHT1
// }
// // MIDDLE1
// a = obj.foo;
// if (param2) {
//   // LEFT2
//   no_escape1();
// } else {
//   // RIGHT2
// }
// // MIDDLE2
// if (param3) {
//   // LEFT3
//   x = obj.foo;
//   no_escape2();
// } else {
//   // RIGHT3
//   x = 0;
// }
// // BRETURN
// b = obj.foo;
// return a + b + x;
TEST_P(UsesOrderDependentTestGroupForThreeItems, RecordPredicatedReplacements3) {
  ScopedObjectAccess soa(Thread::Current());
  VariableSizedHandleScope vshs(soa.Self());
  CreateGraph(&vshs);
  AdjacencyListGraph blks(SetupFromAdjacencyList("entry",
                                                 "exit",
                                                 {{"entry", "left1"},
                                                  {"entry", "right1"},
                                                  {"left1", "middle1"},
                                                  {"right1", "middle1"},
                                                  {"middle1", "left2"},
                                                  {"middle1", "right2"},
                                                  {"left2", "middle2"},
                                                  {"right2", "middle2"},
                                                  {"middle2", "left3"},
                                                  {"middle2", "right3"},
                                                  {"left3", "breturn"},
                                                  {"right3", "breturn"},
                                                  {"breturn", "exit"}}));
#define GET_BLOCK(name) HBasicBlock* name = blks.Get(#name)
  GET_BLOCK(entry);
  GET_BLOCK(left1);
  GET_BLOCK(right1);
  GET_BLOCK(middle1);
  GET_BLOCK(left2);
  GET_BLOCK(right2);
  GET_BLOCK(middle2);
  GET_BLOCK(left3);
  GET_BLOCK(right3);
  GET_BLOCK(breturn);
  GET_BLOCK(exit);
#undef GET_BLOCK
  EnsurePredecessorOrder(middle1, {left1, right1});
  EnsurePredecessorOrder(middle2, {left2, right2});
  EnsurePredecessorOrder(breturn, {left3, right3});
  HInstruction* c0 = graph_->GetIntConstant(0);
  HInstruction* cnull = graph_->GetNullConstant();
  HInstruction* c11 = graph_->GetIntConstant(11);
  HInstruction* param1 = MakeParam(DataType::Type::kBool);
  HInstruction* param2 = MakeParam(DataType::Type::kBool);
  HInstruction* param3 = MakeParam(DataType::Type::kBool);

  HInstruction* suspend = new (GetAllocator()) HSuspendCheck();
  HInstruction* cls = MakeClassLoad();
  HInstruction* new_inst = MakeNewInstance(cls);
  HInstruction* entry_write = MakeIFieldSet(new_inst, c11, MemberOffset(32));
  HInstruction* entry_if = new (GetAllocator()) HIf(param1);
  entry->AddInstruction(suspend);
  entry->AddInstruction(cls);
  entry->AddInstruction(new_inst);
  entry->AddInstruction(entry_write);
  entry->AddInstruction(entry_if);
  ManuallyBuildEnvFor(suspend, {});
  ManuallyBuildEnvFor(cls, {});
  ManuallyBuildEnvFor(new_inst, {});

  HInstruction* left1_call = MakeInvoke(DataType::Type::kVoid, { new_inst });
  HInstruction* left1_goto = new (GetAllocator()) HGoto();
  left1->AddInstruction(left1_call);
  left1->AddInstruction(left1_goto);
  ManuallyBuildEnvFor(left1_call, {});

  HInstruction* right1_goto = new (GetAllocator()) HGoto();
  right1->AddInstruction(right1_goto);

  HInstruction* middle1_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* middle1_if = new (GetAllocator()) HIf(param2);
  // Delay inserting `middle1_read`, do that later with ordering based on `GetParam()`.
  middle1->AddInstruction(middle1_if);

  HInstruction* left2_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* left2_goto = new (GetAllocator()) HGoto();
  left2->AddInstruction(left2_call);
  left2->AddInstruction(left2_goto);
  ManuallyBuildEnvFor(left2_call, {});

  HInstruction* right2_goto = new (GetAllocator()) HGoto();
  right2->AddInstruction(right2_goto);

  HInstruction* middle2_if = new (GetAllocator()) HIf(param3);
  middle2->AddInstruction(middle2_if);

  HInstruction* left3_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* left3_call = MakeInvoke(DataType::Type::kVoid, {});
  HInstruction* left3_goto = new (GetAllocator()) HGoto();
  // Delay inserting `left3_read`, do that later with ordering based on `GetParam()`.
  left3->AddInstruction(left3_call);
  left3->AddInstruction(left3_goto);
  ManuallyBuildEnvFor(left3_call, {});

  HInstruction* right3_goto = new (GetAllocator()) HGoto();
  right3->AddInstruction(right3_goto);

  HPhi* breturn_phi = MakePhi({left3_read, c0});
  HInstruction* breturn_read = MakeIFieldGet(new_inst, DataType::Type::kInt32, MemberOffset(32));
  HInstruction* breturn_add1 =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, middle1_read, breturn_read);
  HInstruction* breturn_add2 =
      new (GetAllocator()) HAdd(DataType::Type::kInt32, breturn_add1, breturn_phi);
  HInstruction* breturn_return = new (GetAllocator()) HReturn(breturn_add2);
  breturn->AddPhi(breturn_phi);
  // Delay inserting `breturn_read`, do that later with ordering based on `GetParam()`.
  breturn->AddInstruction(breturn_add1);
  breturn->AddInstruction(breturn_add2);
  breturn->AddInstruction(breturn_return);

  // Insert reads in the same positions but in different insertion orders.
  // The only difference is the order of entries in `new_inst->GetUses()` which
  // is used by `HeapReferenceData::CollectReplacements()` and defines the order
  // of instructions to process for `HeapReferenceData::PredicateInstructions()`.
  std::tuple<size_t, HInstruction*, HInstruction*> read_insertions[] = {
      { 0u, middle1_read, middle1_if },
      { 1u, left3_read, left3_call },
      { 2u, breturn_read, breturn_add1 },
  };
  for (size_t i = 0, num = GetParam(); i != num; ++i) {
    std::next_permutation(read_insertions, read_insertions + std::size(read_insertions));
  }
  for (auto [order, read, cursor] : read_insertions) {
    cursor->GetBlock()->InsertInstructionBefore(read, cursor);
  }

  SetupExit(exit);

  // PerformLSE expects this to be empty.
  graph_->ClearDominanceInformation();
  LOG(INFO) << "Pre LSE " << blks;
  PerformLSE();
  LOG(INFO) << "Post LSE " << blks;

  EXPECT_INS_RETAINED(cls);
  EXPECT_INS_REMOVED(new_inst);
  HNewInstance* replacement_new_inst = FindSingleInstruction<HNewInstance>(graph_);
  ASSERT_NE(replacement_new_inst, nullptr);
  EXPECT_INS_REMOVED(entry_write);
  HInstanceFieldSet* replacement_write = FindSingleInstruction<HInstanceFieldSet>(graph_);
  ASSERT_NE(replacement_write, nullptr);
  ASSERT_FALSE(replacement_write->GetIsPredicatedSet());
  ASSERT_INS_EQ(replacement_write->InputAt(0), replacement_new_inst);
  ASSERT_INS_EQ(replacement_write->InputAt(1), c11);

  EXPECT_INS_RETAINED(left1_call);

  EXPECT_INS_REMOVED(middle1_read);
  HPredicatedInstanceFieldGet* replacement_middle1_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, middle1);
  ASSERT_NE(replacement_middle1_read, nullptr);
  ASSERT_TRUE(replacement_middle1_read->GetTarget()->IsPhi());
  ASSERT_EQ(2u, replacement_middle1_read->GetTarget()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_middle1_read->GetTarget()->AsPhi()->InputAt(0), replacement_new_inst);
  ASSERT_INS_EQ(replacement_middle1_read->GetTarget()->AsPhi()->InputAt(1), cnull);
  ASSERT_TRUE(replacement_middle1_read->GetDefaultValue()->IsPhi());
  ASSERT_EQ(2u, replacement_middle1_read->GetDefaultValue()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_middle1_read->GetDefaultValue()->AsPhi()->InputAt(0), c0);
  ASSERT_INS_EQ(replacement_middle1_read->GetDefaultValue()->AsPhi()->InputAt(1), c11);

  EXPECT_INS_RETAINED(left2_call);

  EXPECT_INS_REMOVED(left3_read);
  HPredicatedInstanceFieldGet* replacement_left3_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, left3);
  ASSERT_NE(replacement_left3_read, nullptr);
  ASSERT_TRUE(replacement_left3_read->GetTarget()->IsPhi());
  ASSERT_INS_EQ(replacement_left3_read->GetTarget(), replacement_middle1_read->GetTarget());
  ASSERT_INS_EQ(replacement_left3_read->GetDefaultValue(), replacement_middle1_read);
  EXPECT_INS_RETAINED(left3_call);

  EXPECT_INS_RETAINED(breturn_phi);
  EXPECT_INS_REMOVED(breturn_read);
  HPredicatedInstanceFieldGet* replacement_breturn_read =
      FindSingleInstruction<HPredicatedInstanceFieldGet>(graph_, breturn);
  ASSERT_NE(replacement_breturn_read, nullptr);
  ASSERT_INS_EQ(replacement_breturn_read->GetTarget(), replacement_middle1_read->GetTarget());
  ASSERT_EQ(2u, replacement_breturn_read->GetDefaultValue()->AsPhi()->InputCount());
  ASSERT_INS_EQ(replacement_breturn_read->GetDefaultValue()->AsPhi()->InputAt(0),
                replacement_left3_read);
  ASSERT_INS_EQ(replacement_breturn_read->GetDefaultValue()->AsPhi()->InputAt(1),
                replacement_middle1_read);
  EXPECT_INS_RETAINED(breturn_add1);
  ASSERT_INS_EQ(breturn_add1->InputAt(0), replacement_middle1_read);
  ASSERT_INS_EQ(breturn_add1->InputAt(1), replacement_breturn_read);
  EXPECT_INS_RETAINED(breturn_add2);
  ASSERT_INS_EQ(breturn_add2->InputAt(0), breturn_add1);
  ASSERT_INS_EQ(breturn_add2->InputAt(1), breturn_phi);
  EXPECT_INS_RETAINED(breturn_return);
}

INSTANTIATE_TEST_SUITE_P(LoadStoreEliminationTest,
                         UsesOrderDependentTestGroupForThreeItems,
                         testing::Values(0u, 1u, 2u, 3u, 4u, 5u));

}  // namespace art
