Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x64EmitterTest: add J/J_CC/CALL unit tests #11959

Merged
merged 2 commits into from
Aug 21, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
115 changes: 103 additions & 12 deletions Source/UnitTests/Common/x64EmitterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ class x64EmitterTest : public testing::Test
}

void TearDown() override { cpu_info = CPUInfo(); }

void ResetCodeBuffer() { emitter->SetCodePtr(code_buffer, code_buffer_end); }

void ExpectDisassembly(const std::string& expected)
{
std::string disasmed;
Expand Down Expand Up @@ -186,8 +189,16 @@ class x64EmitterTest : public testing::Test

EXPECT_EQ(expected_norm, disasmed_norm);

// Reset code buffer afterwards.
emitter->SetCodePtr(code_buffer, code_buffer_end);
ResetCodeBuffer();
}

void ExpectBytes(const std::vector<u8> expected_bytes)
{
const std::vector<u8> code_bytes(code_buffer, emitter->GetWritableCodePtr());

EXPECT_EQ(expected_bytes, code_bytes);

ResetCodeBuffer();
}

std::unique_ptr<X64CodeBlock> emitter;
Expand Down Expand Up @@ -286,15 +297,13 @@ TEST_F(x64EmitterTest, POP_Register)

TEST_F(x64EmitterTest, JMP)
{
emitter->NOP(6);
emitter->JMP(code_buffer);
ExpectDisassembly("multibyte nop "
"jmp .-8");
emitter->NOP(1);
emitter->JMP(code_buffer, XEmitter::Jump::Short);
ExpectBytes({/* nop */ 0x90, /* short jmp */ 0xeb, /* offset -3 */ 0xfd});

emitter->NOP(6);
emitter->NOP(1);
emitter->JMP(code_buffer, XEmitter::Jump::Near);
ExpectDisassembly("multibyte nop "
"jmp .-11");
ExpectBytes({/* nop */ 0x90, /* near jmp */ 0xe9, /* offset -6 */ 0xfa, 0xff, 0xff, 0xff});
}

TEST_F(x64EmitterTest, JMPptr_Register)
Expand All @@ -306,11 +315,93 @@ TEST_F(x64EmitterTest, JMPptr_Register)
}
}

// TODO: J/SetJumpTarget
TEST_F(x64EmitterTest, J)
{
FixupBranch jump = emitter->J(XEmitter::Jump::Short);
emitter->NOP(1);
emitter->SetJumpTarget(jump);
ExpectBytes({/* short jmp */ 0xeb, /* offset 1 */ 0x1, /* nop */ 0x90});

jump = emitter->J(XEmitter::Jump::Near);
emitter->NOP(1);
emitter->SetJumpTarget(jump);
ExpectBytes({/* near jmp */ 0xe9, /* offset 1 */ 0x1, 0x0, 0x0, 0x0, /* nop */ 0x90});
}

TEST_F(x64EmitterTest, CALL)
{
FixupBranch call = emitter->CALL();
emitter->NOP(6);
emitter->SetJumpTarget(call);
ExpectDisassembly("call .+6 "
"multibyte nop");

// TODO: CALL
const u8* const code_start = emitter->GetCodePtr();
emitter->CALL(code_start + 5);
ExpectDisassembly("call .+0");

// TODO: J_CC
emitter->NOP(6);
emitter->CALL(code_start);
ExpectDisassembly("multibyte nop "
"call .-11");
}

TEST_F(x64EmitterTest, J_CC)
{
for (const auto& [condition_code, condition_name] : ccnames)
{
FixupBranch fixup = emitter->J_CC(condition_code, XEmitter::Jump::Short);
emitter->NOP(1);
emitter->SetJumpTarget(fixup);
const u8 short_jump_condition_opcode = 0x70 + condition_code;
ExpectBytes({short_jump_condition_opcode, /* offset 1 */ 0x1, /* nop */ 0x90});

fixup = emitter->J_CC(condition_code, XEmitter::Jump::Near);
emitter->NOP(1);
emitter->SetJumpTarget(fixup);
const u8 near_jump_condition_opcode = 0x80 + condition_code;
ExpectBytes({/* two byte opcode */ 0x0f, near_jump_condition_opcode, /* offset 1 */ 0x1, 0x0,
0x0, 0x0, /* nop */ 0x90});
}

// Verify a short jump is used when possible and a near jump when needed.
//
// A short jump to a particular address and a near jump to that same address will have different
// offsets. This is because short jumps are 2 bytes and near jumps are 6 bytes, and the offset to
// the target is calculated from the address of the next instruction.

const u8* const code_start = emitter->GetCodePtr();
constexpr int short_jump_bytes = 2;
const u8* const next_byte_after_short_jump_instruction = code_start + short_jump_bytes;

constexpr int longest_backward_short_jump = 0x80;
const u8* const furthest_byte_reachable_with_backward_short_jump =
next_byte_after_short_jump_instruction - longest_backward_short_jump;
emitter->J_CC(CC_O, furthest_byte_reachable_with_backward_short_jump);
ExpectBytes({/* JO opcode */ 0x70, /* offset -128 */ 0x80});

const u8* const closest_byte_requiring_backward_near_jump =
furthest_byte_reachable_with_backward_short_jump - 1;
emitter->J_CC(CC_O, closest_byte_requiring_backward_near_jump);
// This offset is 5 less than the offset for the furthest backward short jump. -1 because this
// target is 1 byte before the short target, and -4 because the address of the next instruction is
// 4 bytes further away from the jump target than it would be with a short jump.
ExpectBytes({/* two byte JO opcode */ 0x0f, 0x80, /* offset -133 */ 0x7b, 0xff, 0xff, 0xff});

constexpr int longest_forward_short_jump = 0x7f;
const u8* const furthest_byte_reachable_with_forward_short_jump =
next_byte_after_short_jump_instruction + longest_forward_short_jump;
emitter->J_CC(CC_O, furthest_byte_reachable_with_forward_short_jump);
ExpectBytes({/* JO opcode */ 0x70, /* offset 127 */ 0x7f});

const u8* const closest_byte_requiring_forward_near_jump =
furthest_byte_reachable_with_forward_short_jump + 1;
emitter->J_CC(CC_O, closest_byte_requiring_forward_near_jump);
// This offset is 3 less than the offset for the furthest forward short jump. +1 because this
// target is 1 byte after the short target, and -4 because the address of the next instruction is
// 4 bytes closer to the jump target than it would be with a short jump.
ExpectBytes({/* two byte JO opcode */ 0x0f, 0x80, /* offset 124 */ 0x7c, 0x0, 0x0, 0x0});
}

TEST_F(x64EmitterTest, SETcc)
{
Expand Down