perfaware/part_1/sim_8086.cpp

540 lines
9.5 KiB
C++
Raw Normal View History

#include "bloat.hpp"
zpl_arena BlobArena {};
#define allocator zpl_arena_allocator( & BlobArena)
void setup()
{
zpl_arena_init_from_allocator( & BlobArena, zpl_heap(), zpl_megabytes(10) );
if ( BlobArena.total_size == 0 )
{
zpl_assert_crash( "Failed to reserve memory for Tests:: BLobArena" );
}
}
void cleanup()
{
zpl_arena_free( & BlobArena);
}
// Binary formatting for literals is used heavily as that its
// how the encoding is conveyed in the hardware reference from intel.
namespace Op
{
#define D_Opcodes \
D_Entry( mov_88 ) \
D_Entry( mov_89 ) \
enum Code : u8
{
Mask = 0b11111100,
mov_88 = 0b10001000,
mov_89 = 0b11000100,
};
char const* str( Code type )
{
switch ( type )
{
case mov_88:
return "mov_88";
case mov_89:
return "mov_89";
}
return Msg_Invalid_Value;
}
ct char const* helper_meumonic( Code type )
{
switch ( type )
{
case mov_88:
case mov_89:
return "mov";
}
return Msg_Invalid_Value;
}
char const* meumonic( Code type )
{
switch ( type )
{
case mov_88:
case mov_89:
return "mov";
}
return Msg_Invalid_Value;
}
ct char const* helper_intuitive( Code type )
{
switch ( type )
{
case mov_88:
case mov_89:
return "move";
}
return Msg_Invalid_Value;
}
char const* intuitive( Code type )
{
switch ( type )
{
case mov_88:
case mov_89:
return "move";
}
return Msg_Invalid_Value;
}
#undef D_Opcodes
}
namespace Field
{
// Effective Address offset
inline
u8 effective_address( u8 reg )
{
return reg << 3;
}
// https://i.imgur.com/drsyYnM.png
enum Direction : u8
{
Mask_Dir = 0b00000010,
Dir_Src = 0b00,
Dir_Dest = 0b10,
};
inline
char const* str_direction( Direction direction )
{
switch ( direction )
{
case Dir_Dest:
return "Destination";
case Dir_Src:
return "Source";
}
return Msg_Invalid_Value;
}
// https://i.imgur.com/9Op8Lnd.png
enum Width : u8
{
Mask_Width = 0b00000001,
Width_Byte = 0b00,
Width_Word = 0b01,
};
inline
char const* str_width( Width width )
{
switch ( width )
{
case Width_Byte:
return "Byte";
case Width_Word:
return "Word";
}
return Msg_Invalid_Value;
}
// https://i.imgur.com/Job2oPd.png
enum Mode : u8
{
Mask_Mode = 0b11000000,
Mode_Mem = 0b00000000,
Mode_Mem8 = 0b01000000,
Mode_Mem16 = 0b10000000,
Mode_Reg = 0b11000000,
};
char const* str_mode( Mode mode )
{
switch (mode)
{
case Mode_Mem:
return "Memory: No Displacement";
case Mode_Mem8:
return "Memory: 8-bit Displacment";
case Mode_Mem16:
return "Memory: 16-bit Displacement";
case Mode_Reg:
return "Register";
}
return Msg_Invalid_Value;
}
#if 0
ct u8 RegMem_AL = 0b00000000;
ct u8 RegMem_AX = 0b00000000;
ct u8 RegMem_BX_SI = 0b00000000;
ct u8 RegMem_BX_SI_D8 = 0b00000000;
ct u8 RegMem_BX_SI_D16 = 0b00000000;
#endif
}
namespace Register
{
ct u8 Mask_Left = 0b00111000;
ct u8 Mask_Right = 0b00000111;
#define D_Opcodes \
D_Entry( AL ) \
D_Entry( CL ) \
D_Entry( DL ) \
D_Entry( BL ) \
D_Entry( AH ) \
D_Entry( CH ) \
D_Entry( DH ) \
D_Entry( BH ) \
D_Entry( AX ) \
D_Entry( CX ) \
D_Entry( DX ) \
D_Entry( BX ) \
D_Entry( SP ) \
D_Entry( BP ) \
D_Entry( SI ) \
D_Entry( DI ) \
enum Type : u8
{
AL = 0b000,
CL = 0b001,
DL = 0b010,
BL = 0b011,
AH = 0b100,
CH = 0b101,
DH = 0b110,
BH = 0b111,
AX = 0b000,
CX = 0b001,
DX = 0b010,
BX = 0b011,
SP = 0b100,
BP = 0b101,
SI = 0b110,
DI = 0b111,
Num = 16
};
char const* meumonic( Type type, u8 Width )
{
static char const*
Type_To_Meumonic[ Num ] =
{
#define D_Entry( Entry_ ) #Entry_,
D_Opcodes
#undef D_Entry
};
return Type_To_Meumonic[ type + Width * 7 + 1 ];
}
char const* intuitive( Type type, u8 Width )
{
static char const*
Type_To_Intuitive[ Num ] =
{
"A.Low",
"C.Low",
"D.Low",
"B.Low",
"A.High",
"C.High",
"D.High",
"B.High",
"A.16",
"C.16",
"D.16",
"B.16",
"Stack.Pointer",
"Stack.Base",
"Source.Index",
"Destination.Index",
};
return Type_To_Intuitive[ type + Width * 7 + 1 ];
}
}
// 8086 Instructions are 1 to 6 bytes in length.
struct POD_Instruction
{
u8 Byte_1;
u8 Byte_2;
u8 Byte_3;
u8 Byte_4;
u8 Byte_5;
u8 Byte_6;
u16 Pad;
};
struct Instruction : public POD_Instruction
{
using Code = Op::Code;
using Direction = Field::Direction;
using Mode = Field::Mode;
using Width = Field::Width;
using Reg = Register::Type;
inline
Direction direction()
{
Direction
direction = cast(Direction, Byte_1 & Field::Mask_Dir);
return direction;
}
inline
Mode mode()
{
Mode mode = cast( Mode, Byte_2 & Field::Mask_Mode );
return mode;
}
inline
Code opcode()
{
Op::Code
opcode = cast( Op::Code, Byte_1 & Op::Mask );
return opcode;
}
inline
Reg operand_left()
{
u8
operand = Byte_2 & Register::Mask_Left;
operand >>= 3;
return cast(Reg, operand);
}
inline
Reg operand_right()
{
u8 operand = Byte_2 & Register::Mask_Right;
return cast( Reg, operand );
}
inline
Width width()
{
Width width = cast( Width, Byte_1 & Field::Mask_Width );
return width;
}
void dissassemble( zpl_string* result_out )
{
using namespace Field;
using namespace Op;
using namespace Register;
Direction dir = direction();
Width width_val = width();
char const* opcode_str = Op::meumonic( opcode() );
char const* direction_str = Field::str_direction( dir );
char const* width_str = Field::str_width( width_val );
char const* mode_str = Field::str_mode( mode() );
char const* operand_left_str = Register::meumonic( operand_left(), width_val );
char const* operand_right_str = Register::meumonic( operand_right(), width_val );
char
binary_string[9];
binary_string[8] = '\0';
#if Build_Debug
str_binary( binary_string, opcode() );
zpl_printf("\nOpcode : %s : %s", binary_string, opcode_str);
str_binary( binary_string, dir );
zpl_printf("\nDirection : %s : %s", binary_string, direction_str);
str_binary( binary_string, width_val );
zpl_printf("\nWidth : %s : %s", binary_string, width_str);
str_binary( binary_string, mode() );
zpl_printf("\nMode : %s : %s", binary_string, mode_str);
str_binary( binary_string, operand_left() );
zpl_printf("\nOperand : %s : %s", binary_string, operand_left_str);
str_binary( binary_string, operand_right() );
zpl_printf("\nOperand EA : %s : %s", binary_string, operand_right_str);
#endif
if ( result_out == nullptr )
return;
if ( * result_out == nullptr )
* result_out = zpl_string_make_reserve( allocator, zpl_kilobytes(1) );
zpl_string assembly = zpl_string_make_reserve( allocator, 32);
assembly = zpl_string_sprintf( allocator, assembly, zpl_kilobytes(1) - 1, "\n%s %s, %s"
, opcode_str
, dir == Dir_Src ? operand_right_str : operand_left_str
, dir == Dir_Src ? operand_left_str : operand_right_str
);
* result_out = zpl_string_append( * result_out, assembly );
#if Build_Debug
zpl_printf("\n\nAssembly: %s\n\n", assembly);
#endif
}
};
namespace Tests
{
void try_mock_instruction()
{
using namespace Field;
using namespace Op;
using namespace Register;
Instruction
mock; // mov CX, BX
mock.Byte_1 = mov_88 | Dir_Src | Field::Width_Word;
mock.Byte_2 = Field::Mode_Reg | effective_address(BX) | CX;
zpl_printf("\n\nAttempting Mock Instruction: mov CX, BX");
print_nl();
print_as_binary( & mock.Byte_1, 1, " " );
print_as_binary( & mock.Byte_2, 1, " " );
print_nl();
zpl_string dissasembly = nullptr;
mock.dissassemble( & dissasembly);
}
void try_blob_single_instruction()
{
zpl_printf("\n\nAttempting to read blob: listing_0037_single_register_mov");
zpl_file_contents
blob = zpl_file_read_contents( allocator, false, "tests/listing_0037_single_register_mov" );
if (blob.data == nullptr )
return;
u32 left = blob.size;
u8* data = cast( u8*, blob.data );
print_nl();
print_as_binary( data, left, " " );
print_nl();
zpl_string dissasembly = nullptr;
Instruction
instr;
instr.Byte_1 = data[0];
instr.Byte_2 = data[1];
instr.dissassemble( & dissasembly);
}
void try_blob_many_instructions()
{
zpl_printf("\n\nAttempting to read blob: listing_0038_many_register_mov");
zpl_file_contents
blob = zpl_file_read_contents( allocator, false, "tests/listing_0038_many_register_mov" );
if (blob.data == nullptr )
return;
u32 left = blob.size;
u8* data = cast( u8*, blob.data );
print_nl();
print_as_binary( data, left, " " );
print_nl();
zpl_string dissasembly = zpl_string_make( allocator, "bits 16\n");
while ( left )
{
Instruction
instr;
instr.Byte_1 = data[0];
instr.Byte_2 = data[1];
instr.dissassemble( & dissasembly);
data += 2;
left -= 2;
}
zpl_printf("\n\nDissassemlby:\n%s", dissasembly);
dissasembly = zpl_string_append_fmt( dissasembly, "\n" );
zpl_printf("\n\nSaving to file");
zpl_file_write_contents("tests/listing_0038_many_register_mov.out.asm"
, dissasembly
, zpl_string_length(dissasembly)
, nullptr
);
}
#undef allocator
}
int main()
{
zpl_printf("sim 8086!");
setup();
zpl_f64 start = zpl_time_rel();
#if 0
Tests::try_mock_instruction();
Tests::try_blob_single_instruction();
#endif
Tests::try_blob_many_instructions();
zpl_f64 end = zpl_time_rel();
zpl_f64 ms = (end - start) * 100;
printf("\n\nElapsed Time: %lf ms", ms);
printf("\n\n");
cleanup();
return 0;
}