//===------ Array.cpp - Array runtime function tests ----------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/Runtime/Metadata.h"
#include "swift/Runtime/HeapObject.h"
#include "swift/Runtime/Concurrent.h"
#include "swift/Runtime/HeapObject.h"
#include "swift/Runtime/Metadata.h"
#include "gtest/gtest.h"

using namespace swift;

namespace swift {
  void installCommonValueWitnesses(const TypeLayout &layout,
                                   ValueWitnessTable *vwtable);
} // namespace swift


static void initialize_pod_witness_table_size_uint32_t_stride_uint64_t(
    ValueWitnessTable &testTable) {
  testTable.size = sizeof(uint32_t);
  testTable.flags = ValueWitnessFlags()
    .withAlignment(alignof(ValueBuffer))
    .withPOD(true)
    .withBitwiseTakable(true)
    .withInlineStorage(true);
  testTable.stride = sizeof(uint64_t);
  installCommonValueWitnesses(*testTable.getTypeLayout(), &testTable);
}

extern "C" void swift_arrayInitWithCopy(OpaqueValue *dest,
                                               OpaqueValue *src, size_t count,
                                               const Metadata *self);

#define COPY_POD_TEST(kind)                                                    \
  ValueWitnessTable pod_witnesses;                                             \
  memset(&pod_witnesses, 0, sizeof(pod_witnesses));                            \
  initialize_pod_witness_table_size_uint32_t_stride_uint64_t(pod_witnesses);   \
  uint64_t srcArray[3] = {0, 1, 2};                                            \
  uint64_t destArray[3] = {0x5A5A5A5AU, 0x5A5A5A5AU, 0x5A5A5A5AU};             \
  FullOpaqueMetadata testMetadata = {{&pod_witnesses},                         \
                                     {{MetadataKind::Opaque}}};                \
  Metadata *metadata = &testMetadata.base;                                     \
  swift_array##kind((OpaqueValue *)destArray, (OpaqueValue *)srcArray, 3,      \
                    metadata);                                                 \
  EXPECT_EQ(destArray[0], 0u);                                                 \
  EXPECT_EQ(destArray[1], 1u);                                                 \
  EXPECT_EQ(destArray[2], 2u);                                                 \
                                                                               \
  EXPECT_EQ(srcArray[0], 0u);                                                  \
  EXPECT_EQ(srcArray[1], 1u);                                                  \
  EXPECT_EQ(srcArray[2], 2u);

TEST(TestArrayCopy, test_swift_arrayInitWithCopy) {
  COPY_POD_TEST(InitWithCopy);
}


namespace {
struct TestObject : HeapObject {
  size_t *Addr;
  size_t Value;
};
}

static SWIFT_CC(swift) void destroyTestObject(SWIFT_CONTEXT HeapObject *_object) {
  auto object = static_cast<TestObject*>(_object);
  assert(object->Addr && "object already deallocated");
  *object->Addr = object->Value;
  object->Addr = nullptr;
  swift_deallocObject(object, sizeof(TestObject), alignof(TestObject) - 1);
}

static const FullMetadata<ClassMetadata> TestClassObjectMetadata = {
  { { &destroyTestObject }, { &VALUE_WITNESS_SYM(Bo) } },
  { { nullptr }, ClassFlags::UsesSwiftRefcounting, 0, 0, 0, 0, 0, 0 }
};

/// Create an object that, when deallocated, stores the given value to
/// the given pointer.
static TestObject *allocTestObject(size_t *addr, size_t value) {
  auto result =
    static_cast<TestObject *>(swift_allocObject(&TestClassObjectMetadata,
                                                sizeof(TestObject),
                                                alignof(TestObject) - 1));
  result->Addr = addr;
  result->Value = value;
  return result;
}

TEST(TestArrayCopy, test_swift_arrayInitWithCopyNoAliasNonPOD) {
  size_t object0Val = 0;
  size_t object1Val = 1;
  HeapObject *object0 = allocTestObject(&object0Val, 2);
  HeapObject *object1 = allocTestObject(&object1Val, 3);

  HeapObject *srcArray[2] = {object0, object1};
  HeapObject *destArray[2] = {(HeapObject *)0x5A5A5A5AU,
                              (HeapObject *)0x5A5A5A5AU};
  const Metadata *metadata = &TestClassObjectMetadata;
  swift_arrayInitWithCopy((OpaqueValue *)destArray, (OpaqueValue *)srcArray, 2,
                          metadata);
  EXPECT_EQ(destArray[0], object0);
  EXPECT_EQ(destArray[1], object1);

  EXPECT_EQ(srcArray[0], object0);
  EXPECT_EQ(srcArray[1], object1);

  EXPECT_EQ(swift_retainCount(object0), 2u);
  EXPECT_EQ(swift_retainCount(object1), 2u);

  swift_release(object0);
  swift_release(object0);
  swift_release(object1);
  swift_release(object1);
  EXPECT_EQ(object0Val, 2u);
  EXPECT_EQ(object1Val, 3u);
}

extern "C" void swift_arrayInitWithTakeNoAlias(OpaqueValue *dest,
                                               OpaqueValue *src, size_t count,
                                               const Metadata *self);
extern "C" void swift_arrayInitWithTakeFrontToBack(OpaqueValue *dest,
                                                   OpaqueValue *src,
                                                   size_t count,
                                                   const Metadata *self);
extern "C" void swift_arrayInitWithTakeBackToFront(OpaqueValue *dest,
                                                   OpaqueValue *src,
                                                   size_t count,
                                                   const Metadata *self);
TEST(TestArrayCopy, test_swift_arrayInitWithTakeNoAliasPOD) {
  COPY_POD_TEST(InitWithTakeNoAlias);
}

TEST(TestArrayCopy, test_swift_arrayInitWithTakeFrontToBackPOD) {
  COPY_POD_TEST(InitWithTakeFrontToBack);
}

TEST(TestArrayCopy, test_swift_arrayInitWithTakeBackToFrontPOD) {
  COPY_POD_TEST(InitWithTakeBackToFront);
}

#define INITWITHTAKE_NONPOD_TEST(copy)                                         \
  size_t object0Val = 0;                                                       \
  size_t object1Val = 1;                                                       \
  HeapObject *object0 = allocTestObject(&object0Val, 2);                       \
  HeapObject *object1 = allocTestObject(&object1Val, 3);                       \
                                                                               \
  HeapObject *srcArray[2] = {object0, object1};                                \
  HeapObject *destArray[2] = {(HeapObject *)0x5A5A5A5AU,                       \
                              (HeapObject *)0x5A5A5A5AU};                      \
  const Metadata *metadata = &TestClassObjectMetadata;                         \
  swift_arrayInitWithTake##copy((OpaqueValue *)destArray,                      \
                                (OpaqueValue *)srcArray, 2, metadata);         \
  EXPECT_EQ(destArray[0], object0);                                            \
  EXPECT_EQ(destArray[1], object1);                                            \
                                                                               \
  EXPECT_EQ(srcArray[0], object0);                                             \
  EXPECT_EQ(srcArray[1], object1);                                             \
                                                                               \
  EXPECT_EQ(swift_retainCount(object0), 1u);                                   \
  EXPECT_EQ(swift_retainCount(object1), 1u);                                   \
                                                                               \
  swift_release(object0);                                                      \
  swift_release(object1);                                                      \
  EXPECT_EQ(object0Val, 2u);                                                   \
  EXPECT_EQ(object1Val, 3u);

TEST(TestArrayCopy, test_swift_arrayInitWithTakeNoAliasNonPOD) {
  INITWITHTAKE_NONPOD_TEST(NoAlias);
}

TEST(TestArrayCopy, test_swift_arrayInitWithTakeFrontToBackNonPOD) {
  INITWITHTAKE_NONPOD_TEST(FrontToBack);
}

TEST(TestArrayCopy, test_swift_arrayInitWithTakeBackToFrontNonPOD) {
  INITWITHTAKE_NONPOD_TEST(BackToFront);
}

extern "C" void swift_arrayAssignWithCopyNoAlias(OpaqueValue *dest, OpaqueValue *src,
                                      size_t count, const Metadata *self);
extern "C" void swift_arrayAssignWithCopyFrontToBack(OpaqueValue *dest, OpaqueValue *src,
                                          size_t count, const Metadata *self);
extern "C" void swift_arrayAssignWithCopyBackToFront(OpaqueValue *dest, OpaqueValue *src,
                                          size_t count, const Metadata *self);

TEST(TestArrayCopy, test_swift_arrayAssignWithCopyNoAliasPOD) {
  COPY_POD_TEST(AssignWithCopyNoAlias);
}

TEST(TestArrayCopy, test_swift_arrayAssignWithCopyFrontToBackPOD) {
  COPY_POD_TEST(AssignWithCopyFrontToBack);
}

TEST(TestArrayCopy, test_swift_arrayAssignWithCopyBackToFrontPOD) {
  COPY_POD_TEST(AssignWithCopyBackToFront);
}

#define ASSIGNWITHCOPY_NONPOD_TEST(copy)                                       \
  size_t object0Val = 0;                                                       \
  size_t object1Val = 1;                                                       \
  HeapObject *object0 = allocTestObject(&object0Val, 2);                       \
  HeapObject *object1 = allocTestObject(&object1Val, 3);                       \
  size_t object2Val = 4;                                                       \
  size_t object3Val = 5;                                                       \
  HeapObject *object2 = allocTestObject(&object2Val, 6);                       \
  HeapObject *object3 = allocTestObject(&object3Val, 7);                       \
                                                                               \
  HeapObject *srcArray[2] = {object0, object1};                                \
  HeapObject *destArray[2] = {object2, object3};                               \
  const Metadata *metadata = &TestClassObjectMetadata;                         \
  swift_arrayAssignWithCopy##copy((OpaqueValue *)destArray,                    \
                                  (OpaqueValue *)srcArray, 2, metadata);       \
  EXPECT_EQ(destArray[0], object0);                                            \
  EXPECT_EQ(destArray[1], object1);                                            \
                                                                               \
  EXPECT_EQ(srcArray[0], object0);                                             \
  EXPECT_EQ(srcArray[1], object1);                                             \
                                                                               \
  EXPECT_EQ(swift_retainCount(object0), 2u);                                   \
  EXPECT_EQ(swift_retainCount(object1), 2u);                                   \
                                                                               \
  EXPECT_EQ(object2Val, 6u);                                                   \
  EXPECT_EQ(object3Val, 7u);                                                   \
                                                                               \
  swift_release(object0);                                                      \
  swift_release(object0);                                                      \
  swift_release(object1);                                                      \
  swift_release(object1);                                                      \
  EXPECT_EQ(object0Val, 2u);                                                   \
  EXPECT_EQ(object1Val, 3u);

TEST(TestArrayCopy, test_swift_arrayAssignWithCopyNoAliasNonPOD) {
  ASSIGNWITHCOPY_NONPOD_TEST(NoAlias);
}

TEST(TestArrayCopy, test_swift_arrayAssignWithCopyFrontToBackNonPOD) {
  ASSIGNWITHCOPY_NONPOD_TEST(FrontToBack);
}

TEST(TestArrayCopy, test_swift_arrayAssignWithCopyBackToFrontNonPOD) {
  ASSIGNWITHCOPY_NONPOD_TEST(BackToFront);
}

extern "C" void swift_arrayAssignWithTake(OpaqueValue *dest, OpaqueValue *src,
                                          size_t count, const Metadata *self);

TEST(TestArrayCopy, test_swift_arrayAssignWithTakePOD) {
  COPY_POD_TEST(AssignWithTake);
}

TEST(TestArrayCopy, test_swift_arrayAssignWithTakeNonPOD) {
  size_t object0Val = 0;
  size_t object1Val = 1;
  HeapObject *object0 = allocTestObject(&object0Val, 2);
  HeapObject *object1 = allocTestObject(&object1Val, 3);
  size_t object2Val = 4;
  size_t object3Val = 5;
  HeapObject *object2 = allocTestObject(&object2Val, 6);
  HeapObject *object3 = allocTestObject(&object3Val, 7);

  HeapObject *srcArray[2] = {object0, object1};
  HeapObject *destArray[2] = {object2, object3};
  const Metadata *metadata = &TestClassObjectMetadata;
  swift_arrayAssignWithTake((OpaqueValue *)destArray, (OpaqueValue *)srcArray,
                            2, metadata);
  EXPECT_EQ(destArray[0], object0);
  EXPECT_EQ(destArray[1], object1);

  EXPECT_EQ(srcArray[0], object0);
  EXPECT_EQ(srcArray[1], object1);

  EXPECT_EQ(swift_retainCount(object0), 1u);
  EXPECT_EQ(swift_retainCount(object1), 1u);

  EXPECT_EQ(object2Val, 6u);
  EXPECT_EQ(object3Val, 7u);

  swift_release(object0);
  swift_release(object1);
  EXPECT_EQ(object0Val, 2u);
  EXPECT_EQ(object1Val, 3u);
}

extern "C" void swift_arrayDestroy(OpaqueValue *begin, size_t count,
                                   const Metadata *self);

TEST(TestArrayCopy, test_swift_arrayDestroyPOD) {
  ValueWitnessTable pod_witnesses;
  memset(&pod_witnesses, 0, sizeof(pod_witnesses));
  initialize_pod_witness_table_size_uint32_t_stride_uint64_t(pod_witnesses);
  uint64_t array[3] = {0, 1, 2};
  FullOpaqueMetadata testMetadata = {{&pod_witnesses},
                                     {{MetadataKind::Opaque}}};
  Metadata *metadata = &testMetadata.base;
  // Let's make sure this does not crash.
  swift_arrayDestroy((OpaqueValue *)array, 3, metadata);
}

TEST(TestArrayCopy, test_swift_arrayDestroyNonPOD) {
  size_t object0Val = 0;
  size_t object1Val = 1;
  HeapObject *object0 = allocTestObject(&object0Val, 2);
  HeapObject *object1 = allocTestObject(&object1Val, 3);
  HeapObject *array[2] = {object0, object1};
  const Metadata *metadata = &TestClassObjectMetadata;
  swift_arrayDestroy((OpaqueValue *)array, 2, metadata);
  EXPECT_EQ(object0Val, 2u);
  EXPECT_EQ(object1Val, 3u);
}
