// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/compiler/ffi/unit_test.h"

#include "platform/syslog.h"
#include "vm/compiler/ffi/native_type.h"
#include "vm/compiler/runtime_api.h"

namespace dart {
namespace compiler {
namespace ffi {

const NativeCompoundType& RunStructTest(dart::Zone* zone,
                                        const char* name,
                                        const NativeTypes& member_types,
                                        intptr_t packing = kMaxInt32) {
  const auto& struct_type =
      NativeStructType::FromNativeTypes(zone, member_types, packing);

  const char* test_result = struct_type.ToCString(zone, /*multi_line=*/true);

  const int kFilePathLength = 100;
  char expectation_file_path[kFilePathLength];
  Utils::SNPrint(expectation_file_path, kFilePathLength,
                 "runtime/vm/compiler/ffi/unit_tests/%s/%s_%s.expect", name,
                 kTargetArchitectureName, kOs);

  if (TestCaseBase::update_expectations) {
    Syslog::Print("Updating %s\n", expectation_file_path);
    WriteToFile(expectation_file_path, test_result);
  }

  char* expectation_file_contents = nullptr;
  ReadFromFile(expectation_file_path, &expectation_file_contents);
  EXPECT_NOTNULL(expectation_file_contents);
  if (expectation_file_contents != nullptr) {
    EXPECT_STREQ(expectation_file_contents, test_result);
    free(expectation_file_contents);
  }

  return struct_type;
}

UNIT_TEST_CASE_WITH_ZONE(NativeType) {
  const auto& native_type = *new (Z) NativePrimitiveType(kInt8);

  EXPECT_EQ(1, native_type.SizeInBytes());
  EXPECT(native_type.IsInt());
  EXPECT(native_type.IsPrimitive());

  EXPECT_STREQ("int8", native_type.ToCString(Z));
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_int8x10) {
  const auto& int8type = *new (Z) NativePrimitiveType(kInt8);

  auto& members = *new (Z) NativeTypes(Z, 10);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);
  members.Add(&int8type);

  const auto& struct_type = RunStructTest(Z, "struct_int8x10", members);

  EXPECT(!struct_type.ContainsHomogeneousFloats());
  EXPECT(!struct_type.ContainsOnlyFloats(Range::StartAndEnd(0, 8)));
  EXPECT_EQ(0, struct_type.NumberOfWordSizeChunksOnlyFloat());
  EXPECT_EQ(
      Utils::RoundUp(struct_type.SizeInBytes(), compiler::target::kWordSize) /
          compiler::target::kWordSize,
      struct_type.NumberOfWordSizeChunksNotOnlyFloat());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_floatx4) {
  const auto& float_type = *new (Z) NativePrimitiveType(kFloat);

  auto& members = *new (Z) NativeTypes(Z, 4);
  members.Add(&float_type);
  members.Add(&float_type);
  members.Add(&float_type);
  members.Add(&float_type);

  const auto& struct_type = RunStructTest(Z, "struct_floatx4", members);

  // This is a homogenous float in the arm and arm64 ABIs.
  //
  // On Arm64 iOS stack alignment of homogenous floats is not word size see
  // runtime/vm/compiler/ffi/unit_tests/struct_floatx4/arm64_ios.expect.
  EXPECT(struct_type.ContainsHomogeneousFloats());

  // On x64, 8-byte parts of the chunks contain only floats and will be passed
  // in FPU registers.
  EXPECT(struct_type.ContainsOnlyFloats(Range::StartAndEnd(0, 8)));
  EXPECT(struct_type.ContainsOnlyFloats(Range::StartAndEnd(8, 16)));
  EXPECT_EQ(struct_type.SizeInBytes() / compiler::target::kWordSize,
            struct_type.NumberOfWordSizeChunksOnlyFloat());
  EXPECT_EQ(0, struct_type.NumberOfWordSizeChunksNotOnlyFloat());
}

// A struct designed to exercise all kinds of alignment rules.
// Note that offset32A (System V ia32, iOS arm) aligns doubles on 4 bytes while
// offset32B (Arm 32 bit and MSVC ia32) aligns on 8 bytes.
// TODO(37271): Support nested structs.
// TODO(37470): Add uncommon primitive data types when we want to support them.
struct VeryLargeStruct {
  //                             size32 size64 offset32A offset32B offset64
  int8_t a;                   // 1              0         0         0
  int16_t b;                  // 2              2         2         2
  int32_t c;                  // 4              4         4         4
  int64_t d;                  // 8              8         8         8
  uint8_t e;                  // 1             16        16        16
  uint16_t f;                 // 2             18        18        18
  uint32_t g;                 // 4             20        20        20
  uint64_t h;                 // 8             24        24        24
  intptr_t i;                 // 4      8      32        32        32
  double j;                   // 8             36        40        40
  float k;                    // 4             44        48        48
  VeryLargeStruct* parent;    // 4      8      48        52        56
  intptr_t numChildren;       // 4      8      52        56        64
  VeryLargeStruct* children;  // 4      8      56        60        72
  int8_t smallLastField;      // 1             60        64        80
                              // sizeof        64        72        88
};

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_VeryLargeStruct) {
  const auto& intptr_type = *new (Z) NativePrimitiveType(
      compiler::target::kWordSize == 4 ? kInt32 : kInt64);

  auto& members = *new (Z) NativeTypes(Z, 15);
  members.Add(new (Z) NativePrimitiveType(kInt8));
  members.Add(new (Z) NativePrimitiveType(kInt16));
  members.Add(new (Z) NativePrimitiveType(kInt32));
  members.Add(new (Z) NativePrimitiveType(kInt64));
  members.Add(new (Z) NativePrimitiveType(kUint8));
  members.Add(new (Z) NativePrimitiveType(kUint16));
  members.Add(new (Z) NativePrimitiveType(kUint32));
  members.Add(new (Z) NativePrimitiveType(kUint64));
  members.Add(&intptr_type);
  members.Add(new (Z) NativePrimitiveType(kDouble));
  members.Add(new (Z) NativePrimitiveType(kFloat));
  members.Add(&intptr_type);
  members.Add(&intptr_type);
  members.Add(&intptr_type);
  members.Add(new (Z) NativePrimitiveType(kInt8));

  RunStructTest(Z, "struct_VeryLargeStruct", members);
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_int8array) {
  const auto& int8type = *new (Z) NativePrimitiveType(kInt8);
  const auto& array_type = *new (Z) NativeArrayType(int8type, 8);

  auto& members = *new (Z) NativeTypes(Z, 1);
  members.Add(&array_type);

  const auto& struct_type = RunStructTest(Z, "struct_int8array", members);

  EXPECT_EQ(8, struct_type.SizeInBytes());
  EXPECT(!struct_type.ContainsHomogeneousFloats());
  EXPECT(!struct_type.ContainsOnlyFloats(Range::StartAndEnd(0, 8)));
  EXPECT_EQ(0, struct_type.NumberOfWordSizeChunksOnlyFloat());
  EXPECT_EQ(
      Utils::RoundUp(struct_type.SizeInBytes(), compiler::target::kWordSize) /
          compiler::target::kWordSize,
      struct_type.NumberOfWordSizeChunksNotOnlyFloat());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_floatarray) {
  const auto& float_type = *new (Z) NativePrimitiveType(kFloat);

  const auto& inner_array_type = *new (Z) NativeArrayType(float_type, 2);

  auto& inner_struct_members = *new (Z) NativeTypes(Z, 1);
  inner_struct_members.Add(&inner_array_type);
  const auto& inner_struct =
      NativeStructType::FromNativeTypes(Z, inner_struct_members);

  const auto& array_type = *new (Z) NativeArrayType(inner_struct, 2);

  auto& members = *new (Z) NativeTypes(Z, 1);
  members.Add(&array_type);

  const auto& struct_type = RunStructTest(Z, "struct_floatarray", members);

  EXPECT_EQ(16, struct_type.SizeInBytes());
  EXPECT(struct_type.ContainsHomogeneousFloats());
  EXPECT(struct_type.ContainsOnlyFloats(Range::StartAndEnd(0, 8)));
  EXPECT_EQ(16 / compiler::target::kWordSize,
            struct_type.NumberOfWordSizeChunksOnlyFloat());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_packed) {
  const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
  const auto& uint16_type = *new (Z) NativePrimitiveType(kUint16);

  auto& members = *new (Z) NativeTypes(Z, 2);
  members.Add(&uint8_type);
  members.Add(&uint16_type);
  const intptr_t packing = 1;

  const auto& struct_type =
      NativeStructType::FromNativeTypes(Z, members, packing);

  // Should be 3 bytes on every platform.
  EXPECT_EQ(3, struct_type.SizeInBytes());
  EXPECT(struct_type.ContainsUnalignedMembers());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_packed_array) {
  const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
  const auto& uint16_type = *new (Z) NativePrimitiveType(kUint16);

  auto& inner_members = *new (Z) NativeTypes(Z, 2);
  inner_members.Add(&uint16_type);
  inner_members.Add(&uint8_type);
  const intptr_t packing = 1;
  const auto& inner_struct_type =
      NativeStructType::FromNativeTypes(Z, inner_members, packing);

  EXPECT_EQ(3, inner_struct_type.SizeInBytes());
  // Non-windows x64 considers this struct as all members aligned, even though
  // its size is not a multiple of its individual member alignment.
  EXPECT(!inner_struct_type.ContainsUnalignedMembers());

  const auto& array_type = *new (Z) NativeArrayType(inner_struct_type, 2);

  auto& members = *new (Z) NativeTypes(Z, 1);
  members.Add(&array_type);
  const auto& struct_type = NativeStructType::FromNativeTypes(Z, members);

  EXPECT_EQ(6, struct_type.SizeInBytes());
  // Non-windows x64 passes this as a struct with unaligned members, because
  // the second element of the array contains unaligned members.
  EXPECT(struct_type.ContainsUnalignedMembers());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_packed_nested) {
  const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
  const auto& uint32_type = *new (Z) NativePrimitiveType(kUint32);

  auto& inner_members = *new (Z) NativeTypes(Z, 2);
  inner_members.Add(&uint32_type);
  inner_members.Add(&uint8_type);
  const intptr_t packing = 1;
  const auto& inner_struct_type =
      NativeStructType::FromNativeTypes(Z, inner_members, packing);

  EXPECT_EQ(5, inner_struct_type.SizeInBytes());
  // Non-windows x64 considers this struct as all members aligned, even though
  // its size is not a multiple of its individual member alignment.
  EXPECT(!inner_struct_type.ContainsUnalignedMembers());

  auto& members = *new (Z) NativeTypes(Z, 2);
  members.Add(&uint8_type);
  members.Add(&inner_struct_type);
  const auto& struct_type = NativeStructType::FromNativeTypes(Z, members);

  EXPECT_EQ(6, struct_type.SizeInBytes());
  // Non-windows x64 passes this as a struct with unaligned members, even
  // though the nested struct itself has all its members aligned in isolation.
  EXPECT(struct_type.ContainsUnalignedMembers());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_union_size) {
  const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
  const auto& uint32_type = *new (Z) NativePrimitiveType(kUint32);

  const auto& array8_bytes = *new (Z) NativeArrayType(uint32_type, 2);
  const auto& array9_bytes = *new (Z) NativeArrayType(uint8_type, 9);

  auto& members = *new (Z) NativeTypes(Z, 2);
  members.Add(&array8_bytes);
  members.Add(&array9_bytes);
  const auto& union_type = NativeUnionType::FromNativeTypes(Z, members);

  // The alignment requirements of the first member and the size of the second
  // member force the size to be rounded up to 12.
  EXPECT_EQ(12, union_type.SizeInBytes());

  EXPECT(!union_type.ContainsUnalignedMembers());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_union_primitive_members) {
  const auto& float_type = *new (Z) NativePrimitiveType(kFloat);

  const auto& float_array_type = *new (Z) NativeArrayType(float_type, 3);

  auto& struct_member_types = *new (Z) NativeTypes(Z, 4);
  struct_member_types.Add(&float_type);
  struct_member_types.Add(&float_type);
  struct_member_types.Add(&float_type);
  struct_member_types.Add(&float_type);
  const auto& struct_type =
      NativeStructType::FromNativeTypes(Z, struct_member_types);

  auto& member_types = *new (Z) NativeTypes(Z, 2);
  member_types.Add(&float_array_type);
  member_types.Add(&struct_type);
  const auto& union_type = NativeUnionType::FromNativeTypes(Z, member_types);

  EXPECT_EQ(16, union_type.SizeInBytes());

  EXPECT_EQ(4, union_type.NumPrimitiveMembersRecursive());
  EXPECT(union_type.FirstPrimitiveMember().Equals(float_type));
  EXPECT(union_type.ContainsHomogeneousFloats());

  EXPECT(!union_type.ContainsUnalignedMembers());
}

UNIT_TEST_CASE_WITH_ZONE(NativeCompoundType_union_unaligned) {
  const auto& uint8_type = *new (Z) NativePrimitiveType(kUint8);
  const auto& uint32_type = *new (Z) NativePrimitiveType(kUint32);

  auto& inner_members = *new (Z) NativeTypes(Z, 2);
  inner_members.Add(&uint8_type);
  inner_members.Add(&uint32_type);
  const intptr_t packing = 1;
  const auto& struct_type =
      NativeStructType::FromNativeTypes(Z, inner_members, packing);

  const auto& array_type = *new (Z) NativeArrayType(uint8_type, 5);

  auto& member_types = *new (Z) NativeTypes(Z, 2);
  member_types.Add(&array_type);
  member_types.Add(&struct_type);
  const auto& union_type = NativeUnionType::FromNativeTypes(Z, member_types);

  EXPECT_EQ(5, union_type.SizeInBytes());
  EXPECT_EQ(1, union_type.AlignmentInBytesField());

  EXPECT(union_type.ContainsUnalignedMembers());
}

}  // namespace ffi
}  // namespace compiler
}  // namespace dart
