// Tests that static array (in)equality is optimized to a memcmp call when valid.
// More importantly: test that memcmp is _not_ used when it is not valid.

// RUN: %ldc -c -output-ll -of=%t.ll %s && FileCheck %s --check-prefix=LLVM < %t.ll
// RUN: %ldc -c -output-s  -of=%t.s  %s && FileCheck %s --check-prefix=ASM  < %t.s
// RUN: %ldc -O3 -run %s

module mod;

struct ThreeBytes
{
    byte a;
    byte b;
    byte c;
}

align(4) struct ThreeBytesAligned
{
    byte a;
    byte b;
    byte c;
}

struct Packed
{
    byte a;
    byte b;
    byte c;
    byte d;
}

struct PackedPacked
{
    Packed a;
    Packed b;
}

struct WithPadding
{
    int b;
    byte a;
}

// LLVM-LABEL: define{{.*}} @{{.*}}two_uints
bool two_uints(ref uint[2] a, const ref uint[2] b)
{
    // LLVM: call i32 @memcmp({{.*}}, {{.*}}, i{{32|64}} 8)
    return a == b;
}

// LLVM-LABEL: define{{.*}} @{{.*}}unequal_two_uints
bool unequal_two_uints(ref uint[2] a, uint[2] b)
{
    // LLVM: call i32 @memcmp({{.*}}, {{.*}}, i{{32|64}} 8)
    return a != b;
}

// LLVM-LABEL: define{{.*}} @{{.*}}two_floats
bool two_floats(float[2] a, float[2] b)
{
    // LLVM-NOT: memcmp
    return a == b;
}

// LLVM-LABEL: define{{.*}} @{{.*}}four_bools
// ASM-LABEL: four_bools{{.*}}:
bool four_bools(bool[4] a, bool[4] b)
{
    // LLVM: call i32 @memcmp({{.*}}, {{.*}}, i{{32|64}} 4)

    // Make sure that LLVM recognizes and optimizes-out the call to memcmp for 4 byte arrays:
    // ASM-NOT: memcmp
    return a == b;
}

// LLVM-LABEL: define{{.*}} @{{.*}}array_of_array
// ASM-LABEL: array_of_array{{.*}}:
bool array_of_array(byte[3][3] a, const byte[3][3] b)
{
    // LLVM: call i32 @memcmp({{.*}}, {{.*}}, i{{32|64}} 9)
    return a == b;
}

// LLVM-LABEL: define{{.*}} @{{.*}}int3_short3
bool int3_short3(int[3] a, short[3] b)
{
    // LLVM-NOT: memcmp
    return a == b;
    // LLVM-LABEL: ret i1
}

// LLVM-LABEL: define{{.*}} @{{.*}}pointer3
bool pointer3(int*[3] a, int*[3] b)
{
    // LLVM: call i32 @memcmp({{.*}}, {{.*}}, i{{32|64}} {{12|24}})
    return a == b;
}

// LLVM-LABEL: define{{.*}} @{{.*}}enum3
enum E : char { a, b, c, d, e, f };
bool enum3(E[3] a, E[3] b)
{
    // LLVM: call i32 @memcmp({{.*}}, {{.*}}, i{{32|64}} 3)
    return a == b;
}

class K {}
// LLVM-LABEL: define{{.*}} @{{.*}}klass2
bool klass2(K[2] a, K[2] b)
{
    // LLVM-NOT: memcmp
    return a == b;
    // LLVM-LABEL: ret i1
}

void main()
{
    uint[2] a = [1, 2];
    uint[2] b = [1, 2];
    uint[2] c = [2, 1];
    assert(two_uints(a, a));
    assert(two_uints(a, b));
    assert(!two_uints(a, c));
    assert(!unequal_two_uints(a, b));
    assert(unequal_two_uints(a, c));

    assert( two_floats([1.0f, 2.0f], [1.0f, 2.0f]));
    assert(!two_floats([1.0f, 2.0f], [2.0f, 1.0f]));

    assert( four_bools([true, false, true, false], [true, false, true, false]));
    assert(!four_bools([true, false, true, false], [true, false, true, true]));

    assert( array_of_array([[1,2,3],[4,5,6],[7,8,9]],[[1,2,3],[4,5,6],[7,8,9]]));
    assert(!array_of_array([[1,2,3],[4,5,6],[7,8,9]],[[6,6,6],[4,5,6],[7,8,9]]));

    assert( int3_short3([1, 2, 3], [1, 2, 3]));
    assert(!int3_short3([1, 2, 3], [3, 2, 3]));

    int aaa = 666;
    int bbb = 333;
    assert( pointer3([&aaa, &bbb, &aaa], [&aaa, &bbb, &aaa]));
    assert(!pointer3([&aaa, &bbb, &aaa], [&bbb, &bbb, &aaa]));

    assert( enum3([E.a, E.e, E.b], [E.a, E.e, E.b]));
    assert(!enum3([E.a, E.e, E.b], [E.a, E.e, E.f]));
}
