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:
parent
7258083c6f
commit
31e6ed17f8
15
010vm.cc
15
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"
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
13
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,
|
||||
|
|
Loading…
Reference in New Issue