about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2020-09-27 21:12:48 -0700
committerKartik Agaram <vc@akkartik.com>2020-09-27 21:12:48 -0700
commit31e6ed17f84ff5b67803e534cde104b331dd495d (patch)
tree8ed6d9fe0eeea6d55489172c10d64909cddd7c05
parent7258083c6fe720b49c0a2b7e00af662dcb8a2d49 (diff)
downloadmu-31e6ed17f84ff5b67803e534cde104b331dd495d.tar.gz
6885 - starting on floating-point instructions
I spent some time deciding on the instructions. x87 is a stack ISA, so
not a good fit for the rest of SubX. So we use SSE instead. They operate
on 32-bit floats, which seems like a good fit.

SSE has a bunch of instructions for operating on up to 4 floats at once.
We'll ignore all that and just focus on so-called scalar instructions.
-rw-r--r--010vm.cc15
-rw-r--r--023float.cc31
-rw-r--r--033check_operands.cc66
-rw-r--r--subx.md13
4 files changed, 115 insertions, 10 deletions
diff --git a/010vm.cc b/010vm.cc
index f00c54b5..7aca164b 100644
--- a/010vm.cc
+++ b/010vm.cc
@@ -3,7 +3,7 @@
 
 //:: registers
 //: assume segment registers are hard-coded to 0
-//: no floating-point, MMX, etc. yet
+//: no MMX, etc.
 
 :(before "End Types")
 enum {
@@ -28,12 +28,21 @@ uint32_t EIP = 1;  // preserve null pointer
 bzero(Reg, sizeof(Reg));
 EIP = 1;  // preserve null pointer
 
+:(before "End Types")
+const int NUM_XMM_REGISTERS = 8;
+float Xmm[NUM_XMM_REGISTERS] = { 0.0 };
+const string Xname[NUM_XMM_REGISTERS] = { "XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7" };
+:(before "End Reset")
+bzero(Xmm, sizeof(Xmm));
+
 :(before "End Help Contents")
 cerr << "  registers\n";
 :(before "End Help Texts")
 put_new(Help, "registers",
-  "SubX currently supports eight 32-bit integer registers. From 0 to 7, they are:\n"
-  "  EAX ECX EDX EBX ESP EBP ESI EDI\n"
+  "SubX supports 16 registers: eight 32-bit integer registers and eight double-precision\n"
+  "floating-point registers. From 0 to 7, they are:\n"
+  "  integer: EAX ECX EDX EBX ESP EBP ESI EDI\n"
+  "  floating point: XMM0 XMM1 XMM2 XMM3 XMM4 XMM5 XMM6 XMM7\n"
   "ESP contains the top of the stack.\n"
   "\n"
   "-- 8-bit registers\n"
diff --git a/023float.cc b/023float.cc
new file mode 100644
index 00000000..e87368cc
--- /dev/null
+++ b/023float.cc
@@ -0,0 +1,31 @@
+//: floating-point operations
+
+:(before "End Initialize Op Names")
+put_new(Name_f3_0f, "2a", "convert integer to floating-point (cvtsi2ss)");
+
+:(code)
+void test_cvtsi2ss() {
+  Reg[EAX].i = 10;
+  run(
+      "== code 0x1\n"
+      // op     ModR/M  SIB   displacement  immediate
+      "f3 0f 2a c0                                    \n"
+      // ModR/M in binary: 11 (direct mode) 000 (XMM0) 000 (EAX)
+  );
+  CHECK_TRACE_CONTENTS(
+      "run: convert r/m32 to XMM0\n"
+      "run: r/m32 is EAX\n"
+      "run: XMM0 is now 10\n"
+  );
+}
+
+:(before "End Three-Byte Opcodes Starting With f3 0f")
+case 0x2a: {  // convert integer to float
+  const uint8_t modrm = next();
+  const uint8_t dest = (modrm>>3)&0x7;
+  trace(Callstack_depth+1, "run") << "convert r/m32 to " << Xname[dest] << end();
+  const int32_t* src = effective_address(modrm);
+  Xmm[dest] = *src;
+  trace(Callstack_depth+1, "run") << Xname[dest] << " is now " << Xmm[dest] << end();
+  break;
+}
diff --git a/033check_operands.cc b/033check_operands.cc
index f46176c7..3e1f12d1 100644
--- a/033check_operands.cc
+++ b/033check_operands.cc
@@ -610,8 +610,23 @@ void check_operands_0f(const line& inst) {
   check_operands_0f(inst, op);
 }
 
-void check_operands_f3(const line& /*unused*/) {
-  raise << "no supported opcodes starting with f3\n" << end();
+void check_operands_f3(const line& inst) {
+  assert(inst.words.at(0).data == "f3");
+  if (SIZE(inst.words) == 1) {
+    raise << "opcode 'f3' requires a second opcode\n" << end();
+    return;
+  }
+  word op = preprocess_op(inst.words.at(1));
+  if (op.data == "0f") {
+    word op2 = preprocess_op(inst.words.at(2));
+    check_operands_f3_0f(inst, op2);
+    return;
+  }
+  if (!contains_key(Name_f3, op.data)) {
+    raise << "unknown 2-byte opcode 'f3 " << op.data << "'\n" << end();
+    return;
+  }
+  check_operands_f3(inst, op);
 }
 
 void test_check_missing_disp32_operand() {
@@ -666,6 +681,15 @@ put_new(Permitted_operands_0f, "9d", 0x01);
 put_new(Permitted_operands_0f, "9e", 0x01);
 put_new(Permitted_operands_0f, "9f", 0x01);
 
+:(before "End Globals")
+map</*op*/string, /*bitvector*/uint8_t> Permitted_operands_f3;
+map</*op*/string, /*bitvector*/uint8_t> Permitted_operands_f3_0f;
+:(before "End Init Permitted Operands")
+//// Class M: using ModR/M byte
+//  imm32 imm8  disp32 |disp16  disp8 subop modrm
+//  0     0     0      |0       0     0     1
+put_new(Permitted_operands_f3_0f, "2a", 0x01);
+
 :(code)
 void check_operands_0f(const line& inst, const word& op) {
   uint8_t expected_bitvector = get(Permitted_operands_0f, op.data);
@@ -678,6 +702,28 @@ void check_operands_0f(const line& inst, const word& op) {
   }
 }
 
+void check_operands_f3(const line& inst, const word& op) {
+  uint8_t expected_bitvector = get(Permitted_operands_f3, op.data);
+  if (HAS(expected_bitvector, MODRM)) {
+    check_operands_modrm(inst, op);
+    compare_bitvector_modrm(inst, expected_bitvector, maybe_name_f3(op));
+  }
+  else {
+    compare_bitvector(inst, CLEAR(expected_bitvector, MODRM), maybe_name_f3(op));
+  }
+}
+
+void check_operands_f3_0f(const line& inst, const word& op) {
+  uint8_t expected_bitvector = get(Permitted_operands_f3_0f, op.data);
+  if (HAS(expected_bitvector, MODRM)) {
+    check_operands_modrm(inst, op);
+    compare_bitvector_modrm(inst, expected_bitvector, maybe_name_f3_0f(op));
+  }
+  else {
+    compare_bitvector(inst, CLEAR(expected_bitvector, MODRM), maybe_name_f3_0f(op));
+  }
+}
+
 string maybe_name_0f(const word& op) {
   if (!is_hex_byte(op)) return "";
   if (!contains_key(Name_0f, op.data)) return "";
@@ -686,6 +732,22 @@ string maybe_name_0f(const word& op) {
   return " ("+s.substr(0, s.find(" ("))+')';
 }
 
+string maybe_name_f3(const word& op) {
+  if (!is_hex_byte(op)) return "";
+  if (!contains_key(Name_f3, op.data)) return "";
+  // strip stuff in parens from the name
+  const string& s = get(Name_f3, op.data);
+  return " ("+s.substr(0, s.find(" ("))+')';
+}
+
+string maybe_name_f3_0f(const word& op) {
+  if (!is_hex_byte(op)) return "";
+  if (!contains_key(Name_f3_0f, op.data)) return "";
+  // strip stuff in parens from the name
+  const string& s = get(Name_f3_0f, op.data);
+  return " ("+s.substr(0, s.find(" ("))+')';
+}
+
 string tolower(const char* s) {
   ostringstream out;
   for (/*nada*/;  *s;  ++s)
diff --git a/subx.md b/subx.md
index bd9f9d1f..a0004972 100644
--- a/subx.md
+++ b/subx.md
@@ -37,14 +37,17 @@ opcodes`.
 
 The registers instructions operate on are as follows:
 
-- Six general-purpose 32-bit registers: `0/eax`, `1/ebx`, `2/ecx`, `3/edx`,
-  `6/esi` and `7/edi`.
+- Six 32-bit integer registers: `0/eax`, `1/ebx`, `2/ecx`, `3/edx`, `6/esi`
+  and `7/edi`.
 - Two additional 32-bit registers: `4/esp` and `5/ebp`. (I suggest you only
   use these to manage the call stack.)
+- Eight 8-bit integer registers aliased with parts of the 32-bit registers:
+  `0/al`, `1/cl`, `2/dl`, `3/bl`, `4/ah`, `5/ch`, `6/dh` and `7/bh`.
+- Eight 32-bit floating-point registers: `xmm0` through `xmm7`.
 
-(SubX doesn't support floating-point registers yet. Intel processors support
-an 8-bit mode, 16-bit mode and 64-bit mode. SubX will never support them.
-There are also _many_ more instructions that SubX will never support.)
+(Intel processors support a 16-bit mode and 64-bit mode. SubX will never
+support them. There are also _many_ more instructions that SubX will never
+support.)
 
 While SubX doesn't provide the usual mnemonics for opcodes, it _does_ provide
 error-checking. If you miss an argument or accidentally add an extra argument,