/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not read 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 <gtest/gtest.h>

#include "data_type.h"
#include "nodes.h"

namespace art {

// Only runtime types other than void are allowed.
static const DataType::Type kTestTypes[] = {
    DataType::Type::kReference,
    DataType::Type::kBool,
    DataType::Type::kInt8,
    DataType::Type::kUint16,
    DataType::Type::kInt16,
    DataType::Type::kInt32,
    DataType::Type::kInt64,
    DataType::Type::kFloat32,
    DataType::Type::kFloat64,
};

/**
 * Tests for the SideEffects class.
 */

//
// Helper methods.
//

void testWriteAndReadSanity(SideEffects write, SideEffects read) {
  EXPECT_FALSE(write.DoesNothing());
  EXPECT_FALSE(read.DoesNothing());

  EXPECT_TRUE(write.DoesAnyWrite());
  EXPECT_FALSE(write.DoesAnyRead());
  EXPECT_FALSE(read.DoesAnyWrite());
  EXPECT_TRUE(read.DoesAnyRead());

  // All-dependences.
  SideEffects all = SideEffects::All();
  EXPECT_TRUE(all.MayDependOn(write));
  EXPECT_FALSE(write.MayDependOn(all));
  EXPECT_FALSE(all.MayDependOn(read));
  EXPECT_TRUE(read.MayDependOn(all));

  // None-dependences.
  SideEffects none = SideEffects::None();
  EXPECT_FALSE(none.MayDependOn(write));
  EXPECT_FALSE(write.MayDependOn(none));
  EXPECT_FALSE(none.MayDependOn(read));
  EXPECT_FALSE(read.MayDependOn(none));
}

void testWriteAndReadDependence(SideEffects write, SideEffects read) {
  testWriteAndReadSanity(write, read);

  // Dependence only in one direction.
  EXPECT_FALSE(write.MayDependOn(read));
  EXPECT_TRUE(read.MayDependOn(write));
}

void testNoWriteAndReadDependence(SideEffects write, SideEffects read) {
  testWriteAndReadSanity(write, read);

  // No dependence in any direction.
  EXPECT_FALSE(write.MayDependOn(read));
  EXPECT_FALSE(read.MayDependOn(write));
}

//
// Actual tests.
//

TEST(SideEffectsTest, All) {
  SideEffects all = SideEffects::All();
  EXPECT_TRUE(all.DoesAnyWrite());
  EXPECT_TRUE(all.DoesAnyRead());
  EXPECT_FALSE(all.DoesNothing());
  EXPECT_TRUE(all.DoesAllReadWrite());
}

TEST(SideEffectsTest, None) {
  SideEffects none = SideEffects::None();
  EXPECT_FALSE(none.DoesAnyWrite());
  EXPECT_FALSE(none.DoesAnyRead());
  EXPECT_TRUE(none.DoesNothing());
  EXPECT_FALSE(none.DoesAllReadWrite());
}

TEST(SideEffectsTest, DependencesAndNoDependences) {
  // Apply test to each individual data type.
  for (DataType::Type type : kTestTypes) {
    // Same data type and access type: proper write/read dep.
    testWriteAndReadDependence(
        SideEffects::FieldWriteOfType(type, false),
        SideEffects::FieldReadOfType(type, false));
    testWriteAndReadDependence(
        SideEffects::ArrayWriteOfType(type),
        SideEffects::ArrayReadOfType(type));
    // Same data type but different access type: no write/read dep.
    testNoWriteAndReadDependence(
        SideEffects::FieldWriteOfType(type, false),
        SideEffects::ArrayReadOfType(type));
    testNoWriteAndReadDependence(
        SideEffects::ArrayWriteOfType(type),
        SideEffects::FieldReadOfType(type, false));
  }
}

TEST(SideEffectsTest, NoDependences) {
  // Different data type, same access type: no write/read dep.
  testNoWriteAndReadDependence(
      SideEffects::FieldWriteOfType(DataType::Type::kInt32, false),
      SideEffects::FieldReadOfType(DataType::Type::kFloat64, false));
  testNoWriteAndReadDependence(
      SideEffects::ArrayWriteOfType(DataType::Type::kInt32),
      SideEffects::ArrayReadOfType(DataType::Type::kFloat64));
  // Everything different: no write/read dep.
  testNoWriteAndReadDependence(
      SideEffects::FieldWriteOfType(DataType::Type::kInt32, false),
      SideEffects::ArrayReadOfType(DataType::Type::kFloat64));
  testNoWriteAndReadDependence(
      SideEffects::ArrayWriteOfType(DataType::Type::kInt32),
      SideEffects::FieldReadOfType(DataType::Type::kFloat64, false));
}

TEST(SideEffectsTest, VolatileDependences) {
  SideEffects volatile_write =
      SideEffects::FieldWriteOfType(DataType::Type::kInt32, /* is_volatile= */ true);
  SideEffects any_write =
      SideEffects::FieldWriteOfType(DataType::Type::kInt32, /* is_volatile= */ false);
  SideEffects volatile_read =
      SideEffects::FieldReadOfType(DataType::Type::kInt8, /* is_volatile= */ true);
  SideEffects any_read =
      SideEffects::FieldReadOfType(DataType::Type::kInt8, /* is_volatile= */ false);

  EXPECT_FALSE(volatile_write.MayDependOn(any_read));
  EXPECT_TRUE(any_read.MayDependOn(volatile_write));
  EXPECT_TRUE(volatile_write.MayDependOn(any_write));
  EXPECT_FALSE(any_write.MayDependOn(volatile_write));

  EXPECT_FALSE(volatile_read.MayDependOn(any_read));
  EXPECT_TRUE(any_read.MayDependOn(volatile_read));
  EXPECT_TRUE(volatile_read.MayDependOn(any_write));
  EXPECT_FALSE(any_write.MayDependOn(volatile_read));
}

TEST(SideEffectsTest, SameWidthTypesNoAlias) {
  // Type I/F.
  testNoWriteAndReadDependence(
      SideEffects::FieldWriteOfType(DataType::Type::kInt32, /* is_volatile= */ false),
      SideEffects::FieldReadOfType(DataType::Type::kFloat32, /* is_volatile= */ false));
  testNoWriteAndReadDependence(
      SideEffects::ArrayWriteOfType(DataType::Type::kInt32),
      SideEffects::ArrayReadOfType(DataType::Type::kFloat32));
  // Type L/D.
  testNoWriteAndReadDependence(
      SideEffects::FieldWriteOfType(DataType::Type::kInt64, /* is_volatile= */ false),
      SideEffects::FieldReadOfType(DataType::Type::kFloat64, /* is_volatile= */ false));
  testNoWriteAndReadDependence(
      SideEffects::ArrayWriteOfType(DataType::Type::kInt64),
      SideEffects::ArrayReadOfType(DataType::Type::kFloat64));
}

TEST(SideEffectsTest, AllWritesAndReads) {
  SideEffects s = SideEffects::None();
  // Keep taking the union of different writes and reads.
  for (DataType::Type type : kTestTypes) {
    s = s.Union(SideEffects::FieldWriteOfType(type, /* is_volatile= */ false));
    s = s.Union(SideEffects::ArrayWriteOfType(type));
    s = s.Union(SideEffects::FieldReadOfType(type, /* is_volatile= */ false));
    s = s.Union(SideEffects::ArrayReadOfType(type));
  }
  EXPECT_TRUE(s.DoesAllReadWrite());
}

TEST(SideEffectsTest, GC) {
  SideEffects can_trigger_gc = SideEffects::CanTriggerGC();
  SideEffects depends_on_gc = SideEffects::DependsOnGC();
  SideEffects all_changes = SideEffects::AllChanges();
  SideEffects all_dependencies = SideEffects::AllDependencies();

  EXPECT_TRUE(depends_on_gc.MayDependOn(can_trigger_gc));
  EXPECT_TRUE(depends_on_gc.Union(can_trigger_gc).MayDependOn(can_trigger_gc));
  EXPECT_FALSE(can_trigger_gc.MayDependOn(depends_on_gc));

  EXPECT_TRUE(depends_on_gc.MayDependOn(all_changes));
  EXPECT_TRUE(depends_on_gc.Union(can_trigger_gc).MayDependOn(all_changes));
  EXPECT_FALSE(can_trigger_gc.MayDependOn(all_changes));
  EXPECT_FALSE(can_trigger_gc.MayDependOn(can_trigger_gc));

  EXPECT_TRUE(all_changes.Includes(can_trigger_gc));
  EXPECT_FALSE(all_changes.Includes(depends_on_gc));
  EXPECT_TRUE(all_dependencies.Includes(depends_on_gc));
  EXPECT_FALSE(all_dependencies.Includes(can_trigger_gc));
}

TEST(SideEffectsTest, BitStrings) {
  EXPECT_STREQ(
      "|||||||",
      SideEffects::None().ToString().c_str());
  EXPECT_STREQ(
      "|GC|DFJISCBZL|DFJISCBZL|GC|DFJISCBZL|DFJISCBZL|",
      SideEffects::All().ToString().c_str());
  EXPECT_STREQ(
      "|||||DFJISCBZL|DFJISCBZL|",
      SideEffects::AllWrites().ToString().c_str());
  EXPECT_STREQ(
      "||DFJISCBZL|DFJISCBZL||||",
      SideEffects::AllReads().ToString().c_str());
  EXPECT_STREQ(
      "||||||L|",
      SideEffects::FieldWriteOfType(DataType::Type::kReference, false).ToString().c_str());
  EXPECT_STREQ(
      "||DFJISCBZL|DFJISCBZL||DFJISCBZL|DFJISCBZL|",
      SideEffects::FieldWriteOfType(DataType::Type::kReference, true).ToString().c_str());
  EXPECT_STREQ(
      "|||||Z||",
      SideEffects::ArrayWriteOfType(DataType::Type::kBool).ToString().c_str());
  EXPECT_STREQ(
      "|||||C||",
      SideEffects::ArrayWriteOfType(DataType::Type::kUint16).ToString().c_str());
  EXPECT_STREQ(
      "|||||S||",
      SideEffects::ArrayWriteOfType(DataType::Type::kInt16).ToString().c_str());
  EXPECT_STREQ(
      "|||B||||",
      SideEffects::FieldReadOfType(DataType::Type::kInt8, false).ToString().c_str());
  EXPECT_STREQ(
      "||D|||||",
      SideEffects::ArrayReadOfType(DataType::Type::kFloat64).ToString().c_str());
  EXPECT_STREQ(
      "||J|||||",
      SideEffects::ArrayReadOfType(DataType::Type::kInt64).ToString().c_str());
  EXPECT_STREQ(
      "||F|||||",
      SideEffects::ArrayReadOfType(DataType::Type::kFloat32).ToString().c_str());
  EXPECT_STREQ(
      "||I|||||",
      SideEffects::ArrayReadOfType(DataType::Type::kInt32).ToString().c_str());
  SideEffects s = SideEffects::None();
  s = s.Union(SideEffects::FieldWriteOfType(DataType::Type::kUint16, /* is_volatile= */ false));
  s = s.Union(SideEffects::FieldWriteOfType(DataType::Type::kInt64, /* is_volatile= */ false));
  s = s.Union(SideEffects::ArrayWriteOfType(DataType::Type::kInt16));
  s = s.Union(SideEffects::FieldReadOfType(DataType::Type::kInt32, /* is_volatile= */ false));
  s = s.Union(SideEffects::ArrayReadOfType(DataType::Type::kFloat32));
  s = s.Union(SideEffects::ArrayReadOfType(DataType::Type::kFloat64));
  EXPECT_STREQ("||DF|I||S|JC|", s.ToString().c_str());
}

}  // namespace art
