From 31e6ed17f84ff5b67803e534cde104b331dd495d Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sun, 27 Sep 2020 21:12:48 -0700 Subject: [PATCH] 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. --- 010vm.cc | 15 ++++++++-- 023float.cc | 31 +++++++++++++++++++++ 033check_operands.cc | 66 ++++++++++++++++++++++++++++++++++++++++++-- subx.md | 13 +++++---- 4 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 023float.cc 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 Permitted_operands_f3; +map 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,