//===- subzero/unittest/AssemblerX8664/ControlFlow.cpp --------------------===//
//
//                        The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "AssemblerX8664/TestUtil.h"

namespace Ice {
namespace X8664 {
namespace Test {
namespace {

TEST_F(AssemblerX8664Test, J) {
#define TestJ(C, Near, Dest, Src0, Value0, Src1, Value1)                       \
  do {                                                                         \
    static constexpr char TestString[] =                                       \
        "(" #C ", " #Near ", " #Dest ", " #Src0 ", " #Value0 ", " #Src1        \
        ", " #Value1 ")";                                                      \
    const bool NearJmp = AssemblerX8664::k##Near##Jump;                        \
    Label ShouldBeTaken;                                                       \
    __ mov(IceType_i32, Encoded_GPR_##Src0(), Immediate(Value0));              \
    __ mov(IceType_i32, Encoded_GPR_##Src1(), Immediate(Value1));              \
    __ mov(IceType_i32, Encoded_GPR_##Dest(), Immediate(0xBEEF));              \
    __ cmp(IceType_i32, Encoded_GPR_##Src0(), Encoded_GPR_##Src1());           \
    __ j(Cond::Br_##C, &ShouldBeTaken, NearJmp);                               \
    __ mov(IceType_i32, Encoded_GPR_##Dest(), Immediate(0xC0FFEE));            \
    __ bind(&ShouldBeTaken);                                                   \
    AssembledTest test = assemble();                                           \
    test.run();                                                                \
    ASSERT_EQ(Value0, test.Src0()) << TestString;                              \
    ASSERT_EQ(Value1, test.Src1()) << TestString;                              \
    ASSERT_EQ(0xBEEFul, test.Dest()) << TestString;                            \
    reset();                                                                   \
  } while (0)

#define TestImpl(Dst, Src0, Src1)                                              \
  do {                                                                         \
    TestJ(o, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(o, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                       \
    TestJ(no, Near, Dst, Src0, 0x1ul, Src1, 0x1ul);                            \
    TestJ(no, Far, Dst, Src0, 0x1ul, Src1, 0x1ul);                             \
    TestJ(b, Near, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                      \
    TestJ(b, Far, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                       \
    TestJ(ae, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                     \
    TestJ(ae, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(e, Near, Dst, Src0, 0x80000000ul, Src1, 0x80000000ul);               \
    TestJ(e, Far, Dst, Src0, 0x80000000ul, Src1, 0x80000000ul);                \
    TestJ(ne, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                     \
    TestJ(ne, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(be, Near, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                     \
    TestJ(be, Far, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                      \
    TestJ(a, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(a, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                       \
    TestJ(s, Near, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                      \
    TestJ(s, Far, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                       \
    TestJ(ns, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                     \
    TestJ(ns, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(p, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(p, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                       \
    TestJ(np, Near, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                     \
    TestJ(np, Far, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                      \
    TestJ(l, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(l, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                       \
    TestJ(ge, Near, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                     \
    TestJ(ge, Far, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                      \
    TestJ(le, Near, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                     \
    TestJ(le, Far, Dst, Src0, 0x80000000ul, Src1, 0x1ul);                      \
    TestJ(g, Near, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                      \
    TestJ(g, Far, Dst, Src0, 0x1ul, Src1, 0x80000000ul);                       \
  } while (0)

  TestImpl(r1, r2, r3);
  TestImpl(r2, r3, r4);
  TestImpl(r3, r4, r5);
  TestImpl(r4, r5, r6);
  TestImpl(r5, r6, r7);
  TestImpl(r6, r7, r8);
  TestImpl(r7, r8, r10);
  TestImpl(r8, r10, r11);
  TestImpl(r10, r11, r12);
  TestImpl(r11, r12, r13);
  TestImpl(r12, r13, r14);
  TestImpl(r13, r14, r15);
  TestImpl(r14, r15, r1);
  TestImpl(r15, r1, r2);

#undef TestImpl
#undef TestJ
}

TEST_F(AssemblerX8664Test, CallImm) {
  __ call(Immediate(16));
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ hlt();
  __ mov(IceType_i32, GPRRegister::Encoded_Reg_eax, Immediate(0xf00f));
  __ popl(GPRRegister::Encoded_Reg_ebx);

  AssembledTest test = assemble();

  test.run();

  EXPECT_EQ(0xF00Fu, test.eax());
}

TEST_F(AssemblerX8664Test, CallReg) {
#define TestImpl(Dst, Src)                                                     \
  do {                                                                         \
    __ call(Immediate(16));                                                    \
    int CallTargetAddr = codeBytesSize() + 12;                                 \
    __ popl(Encoded_GPR_##Dst());                                              \
    __ pushl(Encoded_GPR_##Dst());                                             \
    __ ret();                                                                  \
    for (int I = codeBytesSize(); I < CallTargetAddr; ++I) {                   \
      __ hlt();                                                                \
    }                                                                          \
    __ popl(Encoded_GPR_##Src());                                              \
    __ call(Encoded_GPR_##Src());                                              \
                                                                               \
    AssembledTest test = assemble();                                           \
                                                                               \
    test.run();                                                                \
                                                                               \
    ASSERT_LE(15u, test.Dst() - test.Src()) << "(" #Dst ", " #Src ")";         \
    reset();                                                                   \
  } while (0)

  TestImpl(r1, r2);
  TestImpl(r2, r3);
  TestImpl(r3, r4);
  TestImpl(r4, r5);
  TestImpl(r5, r6);
  TestImpl(r6, r7);
  TestImpl(r7, r8);
  TestImpl(r8, r10);
  TestImpl(r10, r11);
  TestImpl(r11, r12);
  TestImpl(r12, r13);
  TestImpl(r13, r14);
  TestImpl(r14, r15);
  TestImpl(r15, r1);

#undef TestImpl
}

TEST_F(AssemblerX8664Test, CallAddr) {
#define TestImpl(Dst, Src)                                                     \
  do {                                                                         \
    const uint32_t T0 = allocateQword();                                       \
    const uint64_t V0 = 0xA0C0FFEEBEEFFEEFull;                                 \
    const uint32_t T1 = allocateDword();                                       \
    __ call(Immediate(16));                                                    \
    int CallTargetAddr = codeBytesSize() + 12;                                 \
    __ mov(IceType_i8, Encoded_GPR_##Dst##l(), Immediate(0xf4));               \
    __ ret();                                                                  \
    for (int I = codeBytesSize(); I < CallTargetAddr; ++I) {                   \
      __ hlt();                                                                \
    }                                                                          \
    __ mov(IceType_i64, Encoded_GPR_##Dst##q(), dwordAddress(T0));             \
    __ popl(Encoded_GPR_##Src##q());                                           \
    __ mov(IceType_i32, dwordAddress(T1), Encoded_GPR_##Src##d());             \
    __ call(dwordAddress(T1));                                                 \
                                                                               \
    AssembledTest test = assemble();                                           \
    test.setQwordTo(T0, V0);                                                   \
    test.run();                                                                \
                                                                               \
    ASSERT_EQ(0xA0C0FFEEBEEFFEF4ull, test.Dst##q()) << "(" #Dst ", " #Src ")"; \
    reset();                                                                   \
  } while (0)

  TestImpl(r1, r2);
  TestImpl(r2, r3);
  TestImpl(r3, r4);
  TestImpl(r4, r5);
  TestImpl(r5, r6);
  TestImpl(r6, r7);
  TestImpl(r7, r8);
  TestImpl(r8, r10);
  TestImpl(r10, r11);
  TestImpl(r11, r12);
  TestImpl(r12, r13);
  TestImpl(r13, r14);
  TestImpl(r14, r15);
  TestImpl(r15, r1);

#undef TestImpl
}

TEST_F(AssemblerX8664Test, Jmp) {
// TestImplReg uses jmp(Label), so jmp(Label) needs to be tested before it.
#define TestImplAddr(Near)                                                     \
  do {                                                                         \
    Label ForwardJmp;                                                          \
    Label BackwardJmp;                                                         \
    Label Done;                                                                \
                                                                               \
    __ jmp(&ForwardJmp, AssemblerX8664::k##Near##Jump);                        \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ bind(&BackwardJmp);                                                     \
    __ jmp(&Done, AssemblerX8664::k##Near##Jump);                              \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ bind(&ForwardJmp);                                                      \
    __ jmp(&BackwardJmp, AssemblerX8664::k##NearJump);                         \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ bind(&Done);                                                            \
  } while (0)

#define TestImplReg(Dst)                                                       \
  do {                                                                         \
    __ call(Immediate(16));                                                    \
    Label Done;                                                                \
    __ jmp(&Done, AssemblerX8664::kNearJump);                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ popl(Encoded_GPR_##Dst());                                              \
    __ jmp(Encoded_GPR_##Dst());                                               \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ hlt();                                                                  \
    __ bind(&Done);                                                            \
                                                                               \
    AssembledTest test = assemble();                                           \
    test.run();                                                                \
                                                                               \
    reset();                                                                   \
  } while (0)

  TestImplAddr(Near);
  TestImplAddr(Far);

  TestImplReg(r1);
  TestImplReg(r2);
  TestImplReg(r3);
  TestImplReg(r4);
  TestImplReg(r5);
  TestImplReg(r6);
  TestImplReg(r7);
  TestImplReg(r8);
  TestImplReg(r10);
  TestImplReg(r11);
  TestImplReg(r12);
  TestImplReg(r13);
  TestImplReg(r14);
  TestImplReg(r15);

#undef TestImplReg
#undef TestImplAddr
}

} // end of anonymous namespace
} // end of namespace Test
} // end of namespace X8664
} // end of namespace Ice