about summary refs log tree commit diff stats
path: root/subx
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2019-05-12 07:36:18 -0700
committerKartik Agaram <vc@akkartik.com>2019-05-12 07:41:34 -0700
commit8c1a69089bcb0be8ee869a3f83c3151a7083e27c (patch)
tree319e3b04bb5d94b0306e336dff573d6685e8d683 /subx
parentd5d43e044d16335a3a865a8c9f5aea5cbad73360 (diff)
downloadmu-8c1a69089bcb0be8ee869a3f83c3151a7083e27c.tar.gz
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
Diffstat (limited to 'subx')
-rw-r--r--subx/010---vm.cc48
-rw-r--r--subx/013direct_addressing.cc83
-rw-r--r--subx/014indirect_addressing.cc33
-rw-r--r--subx/015immediate_addressing.cc43
-rwxr-xr-xsubx/build2
5 files changed, 147 insertions, 62 deletions
diff --git a/subx/010---vm.cc b/subx/010---vm.cc
index 31e5608f..00a2dee4 100644
--- a/subx/010---vm.cc
+++ b/subx/010---vm.cc
@@ -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();
 }
 
diff --git a/subx/013direct_addressing.cc b/subx/013direct_addressing.cc
index 160ce6d6..ef5ac752 100644
--- a/subx/013direct_addressing.cc
+++ b/subx/013direct_addressing.cc
@@ -31,6 +31,24 @@ case 0x01: {  // add r32 to r/m32
 }
 
 :(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
 // 'Mem' at once.
@@ -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"
   );
 }
 
diff --git a/subx/014indirect_addressing.cc b/subx/014indirect_addressing.cc
index 8f0d3325..b8b3d6ce 100644
--- a/subx/014indirect_addressing.cc
+++ b/subx/014indirect_addressing.cc
@@ -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"
   );
 }
 
diff --git a/subx/015immediate_addressing.cc b/subx/015immediate_addressing.cc
index 16d886e8..97c868bd 100644
--- a/subx/015immediate_addressing.cc
+++ b/subx/015immediate_addressing.cc
@@ -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"
   );
 }
 
diff --git a/subx/build b/subx/build
index 614b07d8..f29518ac 100755
--- a/subx/build
+++ b/subx/build
@@ -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() {