snapshot of carry flag implementation

Tests failing.

This approach seems wrong. I'm not sure even the tests are correct. Also,
some open questions:

1. Should setting the overflow flag always set the carry flag?
2. Should the carry flag only be set on add/subtract/compare, or by all
arithmetic ops?
3. Had to turn off the -ftrapv flag in `build`. Is there a way to detect
overflow without actually causing overflow?

Once we start setting CF correctly we have to implement jump above/below
instructions (8- and 32-bit displacement variants).

https://github.com/akkartik/mu/issues/30
This commit is contained in:
Kartik Agaram 2019-05-12 07:36:18 -07:00
parent d5d43e044d
commit 8c1a69089b
5 changed files with 147 additions and 62 deletions

View File

@ -62,7 +62,10 @@ put_new(Help, "registers",
"- the sign flag (SF): usually set if an arithmetic result is negative, or\n"
" reset if not.\n"
"- the zero flag (ZF): usually set if a result is zero, or reset if not.\n"
"- the overflow flag (OF): usually set if an arithmetic result overflows.\n"
"- the carry flag (CF): usually set if an arithmetic result overflows by just one bit.\n"
" Useful for operating on unsigned numbers.\n"
"- the overflow flag (OF): usually set if an arithmetic result overflows by more than one bit.\n"
" Useful for operating on signed numbers.\n"
"The flag bits are read by conditional jumps.\n"
"\n"
"For complete details on how different instructions update the flags, consult the IA-32\n"
@ -78,9 +81,10 @@ put_new(Help, "registers",
// the subset of x86 flag registers we care about
bool SF = false; // sign flag
bool ZF = false; // zero flag
bool CF = false; // carry flag
bool OF = false; // overflow flag
:(before "End Reset")
SF = ZF = OF = false;
SF = ZF = CF = OF = false;
//: how the flag registers are updated after each instruction
@ -88,25 +92,35 @@ SF = ZF = OF = false;
// Combine 'arg1' and 'arg2' with arithmetic operation 'op' and store the
// result in 'arg1', then update flags.
// beware: no side-effects in args
#define BINARY_ARITHMETIC_OP(op, arg1, arg2) { \
/* arg1 and arg2 must be signed */ \
int64_t tmp = arg1 op arg2; \
arg1 = arg1 op arg2; \
trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << arg1 << end(); \
SF = (arg1 < 0); \
ZF = (arg1 == 0); \
OF = (arg1 != tmp); \
#define BINARY_ARITHMETIC_OP(op, signed_arg1, signed_arg2) { \
cerr << signed_arg1 << " vs " << signed_arg2 << '\n'; \
int64_t signed_full_result = signed_arg1 op signed_arg2; \
signed_arg1 = signed_arg1 op signed_arg2; \
trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << signed_arg1 << end(); \
SF = (signed_arg1 < 0); \
ZF = (signed_arg1 == 0); \
OF = (signed_arg1 != signed_full_result); \
/* CF is more complex */ \
uint32_t unsigned_arg1 = static_cast<uint32_t>(signed_arg1); \
uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2); \
cerr << unsigned_arg1 << " vs " << unsigned_arg2 << '\n'; \
uint32_t unsigned_result = unsigned_arg1 op unsigned_arg2; \
cerr << "result: " << unsigned_result << '\n'; \
uint64_t unsigned_full_result = unsigned_arg1 op unsigned_arg2; \
CF = (unsigned_result != unsigned_full_result); \
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end(); \
}
// Combine 'arg1' and 'arg2' with bitwise operation 'op' and store the result
// in 'arg1', then update flags.
#define BINARY_BITWISE_OP(op, arg1, arg2) { \
/* arg1 and arg2 must be unsigned */ \
arg1 = arg1 op arg2; \
trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << arg1 << end(); \
SF = (arg1 >> 31); \
ZF = (arg1 == 0); \
#define BINARY_BITWISE_OP(op, unsigned_arg1, unsigned_arg2) { \
unsigned_arg1 = unsigned_arg1 op unsigned_arg2; \
trace(Callstack_depth+1, "run") << "storing 0x" << HEXWORD << unsigned_arg1 << end(); \
SF = (unsigned_arg1 >> 31); \
ZF = (unsigned_arg1 == 0); \
CF = false; \
OF = false; \
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end(); \
}
//:: simulated RAM
@ -374,7 +388,7 @@ void dump_registers() {
if (i > 0) out << "; ";
out << " " << i << ": " << std::hex << std::setw(8) << std::setfill('_') << Reg[i].u;
}
out << " -- SF: " << SF << "; ZF: " << ZF << "; OF: " << OF;
out << " -- SF: " << SF << "; ZF: " << ZF << "; CF: " << CF << "; OF: " << OF;
trace(Callstack_depth+1, "run") << out.str() << end();
}

View File

@ -30,6 +30,24 @@ case 0x01: { // add r32 to r/m32
break;
}
:(code)
void test_add_r32_to_r32_unsigned() {
Reg[EAX].i = 0x7fffffff; // largest positive signed number
Reg[EBX].i = 1;
run(
"== 0x1\n" // code segment
// op ModR/M SIB displacement immediate
" 01 d8 \n" // add EBX to EAX
// ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
);
CHECK_TRACE_CONTENTS(
"run: add EBX to r/m32\n"
"run: r/m32 is EAX\n"
"run: storing 0x80000000\n"
"run: SF=1; ZF=0; CF=1; OF=0\n" // carry flag set
);
}
:(code)
// Implement tables 2-2 and 2-3 in the Intel manual, Volume 2.
// We return a pointer so that instructions can write to multiple bytes in
@ -116,6 +134,24 @@ case 0x29: { // subtract r32 from r/m32
break;
}
:(code)
void test_subtract_r32_from_r32_unsigned() {
Reg[EAX].i = 0x7ffffffd;
Reg[EBX].i = 0x7fffffff;
run(
"== 0x1\n" // code segment
// op ModR/M SIB displacement immediate
" 29 d8 \n" // subtract EBX from EAX
// ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
);
CHECK_TRACE_CONTENTS(
"run: subtract EBX from r/m32\n"
"run: r/m32 is EAX\n"
"run: storing 0xfffffffe\n"
"run: SF=1; ZF=0; CF=1; OF=0\n"
);
}
//:: multiply
:(before "End Initialize Op Names")
@ -686,7 +722,7 @@ void test_compare_r32_with_r32_greater() {
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: r/m32 is EAX\n"
"run: SF=0; ZF=0; OF=0\n"
"run: SF=0; ZF=0; CF=0; OF=0\n"
);
}
@ -695,14 +731,23 @@ case 0x39: { // set SF if r/m32 < r32
const uint8_t modrm = next();
const uint8_t reg2 = (modrm>>3)&0x7;
trace(Callstack_depth+1, "run") << "compare " << rname(reg2) << " with r/m32" << end();
const int32_t* arg1 = effective_address(modrm);
const int32_t arg2 = Reg[reg2].i;
const int32_t tmp1 = *arg1 - arg2;
SF = (tmp1 < 0);
ZF = (tmp1 == 0);
const int64_t tmp2 = *arg1 - arg2;
OF = (tmp1 != tmp2);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
const int32_t* signed_arg1 = effective_address(modrm);
const int32_t signed_arg2 = Reg[reg2].i;
cerr << *signed_arg1 << " vs " << signed_arg2 << '\n';
const int32_t signed_difference = *signed_arg1 - signed_arg2;
SF = (signed_difference < 0);
ZF = (signed_difference == 0);
const int64_t signed_full_difference = *signed_arg1 - signed_arg2;
OF = (signed_difference != signed_full_difference);
const uint32_t unsigned_arg1 = static_cast<uint32_t>(*signed_arg1);
const uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
cerr << unsigned_arg1 << " vs " << unsigned_arg2 << '\n';
const uint32_t unsigned_difference = unsigned_arg1 - unsigned_arg2;
cerr << "result: " << unsigned_difference << '\n';
const uint64_t unsigned_full_difference = unsigned_arg1 - unsigned_arg2;
cerr << "full result: " << unsigned_full_difference << '\n';
CF = (unsigned_difference != unsigned_full_difference);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
break;
}
@ -719,7 +764,23 @@ void test_compare_r32_with_r32_lesser() {
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: r/m32 is EAX\n"
"run: SF=1; ZF=0; OF=0\n"
"run: SF=1; ZF=0; CF=0; OF=0\n"
);
}
void test_compare_r32_with_r32_lesser_unsigned() {
Reg[EAX].i = 0x7ffffffd;
Reg[EBX].i = 0x7fffffff;
run(
"== 0x1\n" // code segment
// op ModR/M SIB displacement immediate
" 39 d8 \n" // compare EBX with EAX
// ModR/M in binary: 11 (direct mode) 011 (src EBX) 000 (dest EAX)
);
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: r/m32 is EAX\n"
"run: SF=1; ZF=0; CF=1; OF=0\n"
);
}
@ -735,7 +796,7 @@ void test_compare_r32_with_r32_equal() {
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: r/m32 is EAX\n"
"run: SF=0; ZF=1; OF=0\n"
"run: SF=0; ZF=1; CF=0; OF=0\n"
);
}

View File

@ -320,7 +320,7 @@ void test_compare_mem_at_r32_with_r32_greater() {
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: effective address is 0x00002000 (EAX)\n"
"run: SF=0; ZF=0; OF=0\n"
"run: SF=0; ZF=0; CF=0; OF=0\n"
);
}
@ -339,7 +339,7 @@ void test_compare_mem_at_r32_with_r32_lesser() {
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: effective address is 0x00002000 (EAX)\n"
"run: SF=1; ZF=0; OF=0\n"
"run: SF=1; ZF=0; CF=0; OF=0\n"
);
}
@ -358,7 +358,7 @@ void test_compare_mem_at_r32_with_r32_equal() {
CHECK_TRACE_CONTENTS(
"run: compare EBX with r/m32\n"
"run: effective address is 0x00002000 (EAX)\n"
"run: SF=0; ZF=1; OF=0\n"
"run: SF=0; ZF=1; CF=0; OF=0\n"
);
}
@ -382,7 +382,7 @@ void test_compare_r32_with_mem_at_r32_greater() {
CHECK_TRACE_CONTENTS(
"run: compare r/m32 with EBX\n"
"run: effective address is 0x00002000 (EAX)\n"
"run: SF=0; ZF=0; OF=0\n"
"run: SF=0; ZF=0; CF=0; OF=0\n"
);
}
@ -391,14 +391,19 @@ case 0x3b: { // set SF if r32 < r/m32
const uint8_t modrm = next();
const uint8_t reg1 = (modrm>>3)&0x7;
trace(Callstack_depth+1, "run") << "compare r/m32 with " << rname(reg1) << end();
const int32_t arg1 = Reg[reg1].i;
const int32_t* arg2 = effective_address(modrm);
const int32_t tmp1 = arg1 - *arg2;
SF = (tmp1 < 0);
ZF = (tmp1 == 0);
int64_t tmp2 = arg1 - *arg2;
OF = (tmp1 != tmp2);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
const int32_t signed_arg1 = Reg[reg1].i;
const int32_t* signed_arg2 = effective_address(modrm);
const int32_t signed_difference = signed_arg1 - *signed_arg2;
SF = (signed_difference < 0);
ZF = (signed_difference == 0);
int64_t full_signed_difference = signed_arg1 - *signed_arg2;
OF = (signed_difference != full_signed_difference);
const uint32_t unsigned_arg1 = static_cast<uint32_t>(signed_arg1);
const uint32_t unsigned_arg2 = static_cast<uint32_t>(*signed_arg2);
const uint32_t unsigned_difference = unsigned_arg1 - unsigned_arg2;
const uint64_t full_unsigned_difference = unsigned_arg1 - unsigned_arg2;
CF = (unsigned_difference != full_unsigned_difference);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
break;
}
@ -417,7 +422,7 @@ void test_compare_r32_with_mem_at_r32_lesser() {
CHECK_TRACE_CONTENTS(
"run: compare r/m32 with EBX\n"
"run: effective address is 0x00002000 (EAX)\n"
"run: SF=1; ZF=0; OF=0\n"
"run: SF=1; ZF=0; CF=0; OF=0\n"
);
}
@ -436,7 +441,7 @@ void test_compare_r32_with_mem_at_r32_equal() {
CHECK_TRACE_CONTENTS(
"run: compare r/m32 with EBX\n"
"run: effective address is 0x00002000 (EAX)\n"
"run: SF=0; ZF=1; OF=0\n"
"run: SF=0; ZF=1; CF=0; OF=0\n"
);
}

View File

@ -571,21 +571,26 @@ void test_compare_imm32_with_eax_greater() {
);
CHECK_TRACE_CONTENTS(
"run: compare EAX and imm32 0x0d0c0b07\n"
"run: SF=0; ZF=0; OF=0\n"
"run: SF=0; ZF=0; CF=0; OF=0\n"
);
}
:(before "End Single-Byte Opcodes")
case 0x3d: { // compare EAX with imm32
const int32_t arg1 = Reg[EAX].i;
const int32_t arg2 = next32();
trace(Callstack_depth+1, "run") << "compare EAX and imm32 0x" << HEXWORD << arg2 << end();
const int32_t tmp1 = arg1 - arg2;
SF = (tmp1 < 0);
ZF = (tmp1 == 0);
const int64_t tmp2 = arg1 - arg2;
OF = (tmp1 != tmp2);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
const int32_t signed_arg1 = Reg[EAX].i;
const int32_t signed_arg2 = next32();
trace(Callstack_depth+1, "run") << "compare EAX and imm32 0x" << HEXWORD << signed_arg2 << end();
const int32_t signed_difference = signed_arg1 - signed_arg2;
SF = (signed_difference < 0);
ZF = (signed_difference == 0);
const int64_t full_signed_difference = signed_arg1 - signed_arg2;
OF = (signed_difference != full_signed_difference);
const uint32_t unsigned_arg1 = static_cast<uint32_t>(signed_arg1);
const uint32_t unsigned_arg2 = static_cast<uint32_t>(signed_arg2);
const uint32_t unsigned_difference = unsigned_arg1 - unsigned_arg2;
const uint64_t full_unsigned_difference = unsigned_arg1 - unsigned_arg2;
CF = (unsigned_difference != full_unsigned_difference);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
break;
}
@ -599,7 +604,7 @@ void test_compare_imm32_with_eax_lesser() {
);
CHECK_TRACE_CONTENTS(
"run: compare EAX and imm32 0x0d0c0b0a\n"
"run: SF=1; ZF=0; OF=0\n"
"run: SF=1; ZF=0; CF=0; OF=0\n"
);
}
@ -613,7 +618,7 @@ void test_compare_imm32_with_eax_equal() {
);
CHECK_TRACE_CONTENTS(
"run: compare EAX and imm32 0x0d0c0b0a\n"
"run: SF=0; ZF=1; OF=0\n"
"run: SF=0; ZF=1; CF=0; OF=0\n"
);
}
@ -632,7 +637,7 @@ void test_compare_imm32_with_r32_greater() {
"run: combine imm32 with r/m32\n"
"run: r/m32 is EBX\n"
"run: imm32 is 0x0d0c0b07\n"
"run: SF=0; ZF=0; OF=0\n"
"run: SF=0; ZF=0; CF=0; OF=0\n"
);
}
@ -644,7 +649,7 @@ case 7: {
ZF = (tmp1 == 0);
const int64_t tmp2 = *arg1 - arg2;
OF = (tmp1 != tmp2);
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; OF=" << OF << end();
trace(Callstack_depth+1, "run") << "SF=" << SF << "; ZF=" << ZF << "; CF=" << CF << "; OF=" << OF << end();
break;
}
@ -661,7 +666,7 @@ void test_compare_imm32_with_r32_lesser() {
"run: combine imm32 with r/m32\n"
"run: r/m32 is EBX\n"
"run: imm32 is 0x0d0c0b0a\n"
"run: SF=1; ZF=0; OF=0\n"
"run: SF=1; ZF=0; CF=0; OF=0\n"
);
}
@ -678,7 +683,7 @@ void test_compare_imm32_with_r32_equal() {
"run: combine imm32 with r/m32\n"
"run: r/m32 is EBX\n"
"run: imm32 is 0x0d0c0b0a\n"
"run: SF=0; ZF=1; OF=0\n"
"run: SF=0; ZF=1; CF=0; OF=0\n"
);
}
@ -697,7 +702,7 @@ void test_compare_imm32_with_mem_at_r32_greater() {
"run: combine imm32 with r/m32\n"
"run: effective address is 0x00002000 (EBX)\n"
"run: imm32 is 0x0d0c0b07\n"
"run: SF=0; ZF=0; OF=0\n"
"run: SF=0; ZF=0; CF=0; OF=0\n"
);
}
@ -716,7 +721,7 @@ void test_compare_imm32_with_mem_at_r32_lesser() {
"run: combine imm32 with r/m32\n"
"run: effective address is 0x00002000 (EBX)\n"
"run: imm32 is 0x0d0c0b0a\n"
"run: SF=1; ZF=0; OF=0\n"
"run: SF=1; ZF=0; CF=0; OF=0\n"
);
}
@ -736,7 +741,7 @@ void test_compare_imm32_with_mem_at_r32_equal() {
"run: combine imm32 with r/m32\n"
"run: effective address is 0x00002000 (EBX)\n"
"run: imm32 is 0x0d0c0b0a\n"
"run: SF=0; ZF=1; OF=0\n"
"run: SF=0; ZF=1; CF=0; OF=0\n"
);
}

View File

@ -22,7 +22,7 @@ UNTIL_LAYER=${2:-zzz}
test "$CXX" || export CXX=c++
test "$CC" || export CC=cc
test "$CFLAGS" || export CFLAGS="-g -O3"
export CFLAGS="$CFLAGS -Wall -Wextra -ftrapv -fno-strict-aliasing"
export CFLAGS="$CFLAGS -Wall -Wextra -fno-strict-aliasing"
# return 1 if $1 is older than _any_ of the remaining args
older_than() {