/*
 * 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 "base/arena_allocator.h"
#include "builder.h"
#include "licm.h"
#include "nodes.h"
#include "optimizing_unit_test.h"
#include "side_effects_analysis.h"

namespace art {

/**
 * Fixture class for the LICM tests.
 */
class LICMTest : public CommonCompilerTest {
 public:
  LICMTest()
      : pool_(),
        allocator_(&pool_),
        entry_(nullptr),
        loop_preheader_(nullptr),
        loop_header_(nullptr),
        loop_body_(nullptr),
        return_(nullptr),
        exit_(nullptr),
        parameter_(nullptr),
        int_constant_(nullptr),
        float_constant_(nullptr) {
    graph_ = CreateGraph(&allocator_);
  }

  ~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() {
    entry_ = new (&allocator_) HBasicBlock(graph_);
    loop_preheader_ = new (&allocator_) HBasicBlock(graph_);
    loop_header_ = new (&allocator_) HBasicBlock(graph_);
    loop_body_ = new (&allocator_) HBasicBlock(graph_);
    return_ = new (&allocator_) HBasicBlock(graph_);
    exit_ = new (&allocator_) HBasicBlock(graph_);

    graph_->AddBlock(entry_);
    graph_->AddBlock(loop_preheader_);
    graph_->AddBlock(loop_header_);
    graph_->AddBlock(loop_body_);
    graph_->AddBlock(return_);
    graph_->AddBlock(exit_);

    graph_->SetEntryBlock(entry_);
    graph_->SetExitBlock(exit_);

    // Set up loop flow in CFG.
    entry_->AddSuccessor(loop_preheader_);
    loop_preheader_->AddSuccessor(loop_header_);
    loop_header_->AddSuccessor(loop_body_);
    loop_header_->AddSuccessor(return_);
    loop_body_->AddSuccessor(loop_header_);
    return_->AddSuccessor(exit_);

    // Provide boiler-plate instructions.
    parameter_ = new (&allocator_) HParameterValue(graph_->GetDexFile(),
                                                   dex::TypeIndex(0),
                                                   0,
                                                   Primitive::kPrimNot);
    entry_->AddInstruction(parameter_);
    int_constant_ = graph_->GetIntConstant(42);
    float_constant_ = graph_->GetFloatConstant(42.0f);
    loop_preheader_->AddInstruction(new (&allocator_) HGoto());
    loop_header_->AddInstruction(new (&allocator_) HIf(parameter_));
    loop_body_->AddInstruction(new (&allocator_) HGoto());
    return_->AddInstruction(new (&allocator_) HReturnVoid());
    exit_->AddInstruction(new (&allocator_) HExit());
  }

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

  // General building fields.
  ArenaPool pool_;
  ArenaAllocator allocator_;
  HGraph* graph_;

  // Specific basic blocks.
  HBasicBlock* entry_;
  HBasicBlock* loop_preheader_;
  HBasicBlock* loop_header_;
  HBasicBlock* loop_body_;
  HBasicBlock* return_;
  HBasicBlock* exit_;

  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 = new (&allocator_) HInstanceFieldGet(parameter_,
                                                                nullptr,
                                                                Primitive::kPrimLong,
                                                                MemberOffset(10),
                                                                false,
                                                                kUnknownFieldIndex,
                                                                kUnknownClassDefIndex,
                                                                graph_->GetDexFile(),
                                                                0);
  loop_body_->InsertInstructionBefore(get_field, loop_body_->GetLastInstruction());
  HInstruction* set_field = new (&allocator_) HInstanceFieldSet(
      parameter_, int_constant_, nullptr, Primitive::kPrimInt, MemberOffset(20),
      false, kUnknownFieldIndex, kUnknownClassDefIndex, graph_->GetDexFile(), 0);
  loop_body_->InsertInstructionBefore(set_field, loop_body_->GetLastInstruction());

  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 = new (&allocator_) HInstanceFieldGet(parameter_,
                                                                nullptr,
                                                                Primitive::kPrimLong,
                                                                MemberOffset(10),
                                                                false,
                                                                kUnknownFieldIndex,
                                                                kUnknownClassDefIndex,
                                                                graph_->GetDexFile(),
                                                                0);
  loop_body_->InsertInstructionBefore(get_field, loop_body_->GetLastInstruction());
  HInstruction* set_field = new (&allocator_) HInstanceFieldSet(parameter_,
                                                                get_field,
                                                                nullptr,
                                                                Primitive::kPrimLong,
                                                                MemberOffset(10),
                                                                false,
                                                                kUnknownFieldIndex,
                                                                kUnknownClassDefIndex,
                                                                graph_->GetDexFile(),
                                                                0);
  loop_body_->InsertInstructionBefore(set_field, loop_body_->GetLastInstruction());

  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 = new (&allocator_) HArrayGet(
      parameter_, int_constant_, Primitive::kPrimInt, 0);
  loop_body_->InsertInstructionBefore(get_array, loop_body_->GetLastInstruction());
  HInstruction* set_array = new (&allocator_) HArraySet(
      parameter_, int_constant_, float_constant_, Primitive::kPrimFloat, 0);
  loop_body_->InsertInstructionBefore(set_array, loop_body_->GetLastInstruction());

  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 = new (&allocator_) HArrayGet(
      parameter_, int_constant_, Primitive::kPrimFloat, 0);
  loop_body_->InsertInstructionBefore(get_array, loop_body_->GetLastInstruction());
  HInstruction* set_array = new (&allocator_) HArraySet(
      parameter_, get_array, float_constant_, Primitive::kPrimFloat, 0);
  loop_body_->InsertInstructionBefore(set_array, loop_body_->GetLastInstruction());

  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
