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.
This commit is contained in:
Kartik Agaram 2020-09-27 21:12:48 -07:00
parent 7258083c6f
commit 31e6ed17f8
4 changed files with 115 additions and 10 deletions

View File

@ -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"

31
023float.cc Normal file
View File

@ -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;
}

View File

@ -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)

13
subx.md
View File

@ -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,