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

#include "base/arena_allocator.h"
#include "base/macros.h"
#include "builder.h"
#include "nodes.h"
#include "optimizing_unit_test.h"
#include "side_effects_analysis.h"

namespace art HIDDEN {

/**
 * Fixture class for the LICM tests.
 */
class LICMTest : public OptimizingUnitTest {
 public:
  LICMTest()
      : loop_preheader_(nullptr),
        loop_header_(nullptr),
        loop_body_(nullptr),
        parameter_(nullptr),
        int_constant_(nullptr),
        float_constant_(nullptr) {
    graph_ = CreateGraph();
  }

  ~LICMTest() { }

  // Builds a singly-nested loop structure in CFG. Tests can further populate
  // the basic blocks with instructions to set up interesting scenarios.
  void BuildLoop() {
    HBasicBlock* return_block = InitEntryMainExitGraphWithReturnVoid();
    std::tie(loop_preheader_, loop_header_, loop_body_) = CreateWhileLoop(return_block);
    loop_header_->SwapSuccessors();  // Move the loop exit to the "else" successor.

    // Provide boiler-plate instructions.
    parameter_ = MakeParam(DataType::Type::kReference);
    int_constant_ = graph_->GetIntConstant(42);
    float_constant_ = graph_->GetFloatConstant(42.0f);
    MakeIf(loop_header_, parameter_);
  }

  // Performs LICM optimizations (after proper set up).
  void PerformLICM() {
    graph_->BuildDominatorTree();
    SideEffectsAnalysis side_effects(graph_);
    side_effects.Run();
    LICM(graph_, side_effects, nullptr).Run();
  }

  // Specific basic blocks.
  HBasicBlock* loop_preheader_;
  HBasicBlock* loop_header_;
  HBasicBlock* loop_body_;

  HInstruction* parameter_;  // "this"
  HInstruction* int_constant_;
  HInstruction* float_constant_;
};

//
// The actual LICM tests.
//

TEST_F(LICMTest, FieldHoisting) {
  BuildLoop();

  // Populate the loop with instructions: set/get field with different types.
  HInstruction* get_field =
      MakeIFieldGet(loop_body_, parameter_, DataType::Type::kInt64, MemberOffset(10));
  HInstruction* set_field =
     MakeIFieldSet(loop_body_, parameter_, int_constant_, DataType::Type::kInt32, MemberOffset(20));

  EXPECT_EQ(get_field->GetBlock(), loop_body_);
  EXPECT_EQ(set_field->GetBlock(), loop_body_);
  PerformLICM();
  EXPECT_EQ(get_field->GetBlock(), loop_preheader_);
  EXPECT_EQ(set_field->GetBlock(), loop_body_);
}

TEST_F(LICMTest, NoFieldHoisting) {
  BuildLoop();

  // Populate the loop with instructions: set/get field with same types.
  ScopedNullHandle<mirror::DexCache> dex_cache;
  HInstruction* get_field =
      MakeIFieldGet(loop_body_, parameter_, DataType::Type::kInt64, MemberOffset(10));
  HInstruction* set_field = MakeIFieldSet(loop_body_, parameter_, get_field, MemberOffset(10));

  EXPECT_EQ(get_field->GetBlock(), loop_body_);
  EXPECT_EQ(set_field->GetBlock(), loop_body_);
  PerformLICM();
  EXPECT_EQ(get_field->GetBlock(), loop_body_);
  EXPECT_EQ(set_field->GetBlock(), loop_body_);
}

TEST_F(LICMTest, ArrayHoisting) {
  BuildLoop();

  // Populate the loop with instructions: set/get array with different types.
  HInstruction* get_array =
      MakeArrayGet(loop_body_, parameter_, int_constant_, DataType::Type::kInt32);
  HInstruction* set_array = MakeArraySet(
      loop_body_, parameter_, int_constant_, float_constant_, DataType::Type::kFloat32);

  EXPECT_EQ(get_array->GetBlock(), loop_body_);
  EXPECT_EQ(set_array->GetBlock(), loop_body_);
  PerformLICM();
  EXPECT_EQ(get_array->GetBlock(), loop_preheader_);
  EXPECT_EQ(set_array->GetBlock(), loop_body_);
}

TEST_F(LICMTest, NoArrayHoisting) {
  BuildLoop();

  // Populate the loop with instructions: set/get array with same types.
  HInstruction* get_array =
      MakeArrayGet(loop_body_, parameter_, int_constant_, DataType::Type::kFloat32);
  HInstruction* set_array =
      MakeArraySet(loop_body_, parameter_, get_array, float_constant_, DataType::Type::kFloat32);

  EXPECT_EQ(get_array->GetBlock(), loop_body_);
  EXPECT_EQ(set_array->GetBlock(), loop_body_);
  PerformLICM();
  EXPECT_EQ(get_array->GetBlock(), loop_body_);
  EXPECT_EQ(set_array->GetBlock(), loop_body_);
}

}  // namespace art
