#include "bloat.hpp" // Octal formatting is used heavily throughout // See: https://gist.github.com/seanjensengrey/f971c20d05d4d0efc0781f2f3c0353da // (x86 is an octal machine) namespace Op { ct u8 Mask = 00210; ct u8 Mask_Low = 0b11110000; ct u8 Mask_High = 0b00001111; ct u8 Mask_Imme = 0b00111000; #define D_Opcodes \ D_Entry( mov_RM_R ) \ D_Entry( mov_Im_RM ) \ D_Entry( mov_Im_R ) \ D_Entry( mov_M_Acc ) \ D_Entry( mov_Acc_M ) \ D_Entry( mov_RM_SR ) \ D_Entry( mov_SR_RM ) \ enum Code : u8 { mov_RM_R = 0b10001000, mov_Im_RM = 0b11001100, mov_Im_R = 0b10110000, mov_M_Acc = 0b10100000, mov_Acc_M = 0b10100010, mov_RM_SR = 0b10001110, mov_SR_RM = 0b10001100 }; char const* str( Code type ) { switch ( type ) { #define D_Entry( Entry_ ) \ case Entry_: \ return ZPL_STRINGIFY(Entry_); \ D_Opcodes #undef D_Entry } return Msg_Invalid_Value; } char const* meumonic( Code type ) { switch ( type ) { case mov_RM_R : case mov_Im_RM : case mov_Im_R : case mov_M_Acc : case mov_Acc_M : case mov_RM_SR : case mov_SR_RM : return "mov"; } return Msg_Invalid_Value; } char const* intuitive( Code type ) { switch ( type ) { case mov_RM_R : case mov_Im_RM : case mov_Im_R : case mov_M_Acc : case mov_Acc_M : case mov_RM_SR : case mov_SR_RM : return "move"; } return Msg_Invalid_Value; } #undef D_Opcodes } namespace Field { inline u8 offset_left_reg( 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, Mask_Width_Imme = 0b00001000, 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; } ct u8 Mask_Memory = 0b00000111; // https://i.imgur.com/Tm5roJu.png enum Memory : u8 { Add_BX_SI = 0b000, Add_BX_DI = 0b001, Add_BP_SI = 0b010, Add_BP_DI = 0b011, Add_SI = 0b100, Add_DI = 0b101, Add_Direct = 0b110, Add_BX = 0b111, Num_Memory }; char const* str_memory( Memory mem ) { char const* mem_to_str[Num_Memory] = { "BX + SI", "BX + DI", "BP + SI", "BP + DI", "SI", "DI", "BP", "BX" }; return mem_to_str[ mem ]; } char const* str_memory_intuitive( Memory mem ) { char const* mem_to_str[Num_Memory] = { "Base.16 + Stack.Index", "Base.16 + Destination.Index", "Stack.Base + Stack.Index", "Stack.Base + Destination.Index", "Stack.Index", "Destination.Index", "Stack.Base", "BX" }; return mem_to_str[ mem ]; } } namespace Register { ct u8 Mask_Imme = 0b00000111; 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 ] = { "Accumulator.Low", "Count.Low", "Data.Low", "Base.Low", "Accumulator.High", "Count.High", "Data.High", "Base.High", "Accumulator.16", "Count.16", "Data.16", "Base.16", "Stack.Pointer", "Stack.Base", "Source.Index", "Destination.Index", }; return Type_To_Intuitive[ type + Width * ( 7 + 1 ) ]; } } struct Octal { u8 Low : 3; u8 High : 3; u8 Dir : 1; u8 Write : 1; operator u8() { return * cast( u8*, this); } }; struct POD_Instruction { // 8086 Instructions are 1 to 6 bytes in length. union { u8* Ptr; u8 Byte[6]; Octal Instr; struct { u8 Pad[2]; u16_Split Disp; u16_Split Data; } EffAddr; struct { u8 Pad[1]; u16_Split Data; } Imme; struct { u8 Pad; u16_Split Data; } Addr; }; // If directly referencing blob data: // Part of next instruction, do not use as stratch memory. u16 Pad; }; struct Instruction : public POD_Instruction { using Code = Op::Code; using Direction = Field::Direction; using Mode = Field::Mode; using Memory = Field::Memory; using Width = Field::Width; using Reg = Register::Type; inline Direction get_direction() { Direction direction = cast(Direction, Byte[0] & Field::Mask_Dir); return direction; } inline Mode get_mode() { Mode mode = cast( Mode, Byte[1] & Field::Mask_Mode ); return mode; } inline Code get_opcode( u8 mask ) { Op::Code opcode = cast( Op::Code, Byte[0] & mask ); return opcode; } inline Width get_width( u8 mask ) { Width width = cast( Width, Byte[0] & mask ); return width; } inline Reg mode_operand_left_reg() { u8 operand = Byte[1] & Register::Mask_Left; operand >>= 3; return cast(Reg, operand); } inline Reg mode_operand_right_reg() { u8 operand = Byte[1] & Register::Mask_Right; return cast( Reg, operand ); } forceinline void dissassemble_mode( char const*& str_operand, Width& width, u8& length ) { using namespace Field; using namespace Op; using namespace Register; switch ( get_mode() ) { case Mode_Mem: { Memory operand_right = cast( Memory, Byte[1] & Mask_Memory ); length += 2; if ( operand_right == Add_Direct ) { u16 address = EffAddr.Disp; str_operand = string_format( "[ %u ]", address ); } else { str_operand = str_memory( operand_right ); } } break; case Mode_Mem8: { Memory operand_right = cast( Memory, Byte[1] & Mask_Memory ); length += 1; str_operand = string_format( "[ %s + %u ]", str_memory( operand_right), EffAddr.Disp.Low ); } break; case Mode_Mem16: { length += 2; Memory operand_right = cast( Memory, Byte[1] & Mask_Memory ); u16 address = EffAddr.Disp; str_operand = string_format( "[ %s + %u ]", str_memory( operand_right), address ); } break; case Mode_Reg: str_operand = Register::meumonic( mode_operand_right_reg(), width ); break; } } u8 dissassemble( zpl_string* result_out ) { using namespace Field; using namespace Op; using namespace Register; u8 length = 1; zpl_string assembly = string_make( 32); Code code = get_opcode( Op::Mask ); Direction dir = get_direction(); char const* str_operand_left = nullptr; char const* str_operand_right = nullptr; const u8 code_low = Byte[0] & Op::Mask_Low; const u8 code_high = Byte[0] & Op::Mask_High; const u8 sig_imme = Byte[0] & Op::Mask_Imme; if ( get_opcode( Op::Mask_Low ) == mov_Im_R ) { Width width = get_width( Mask_Width_Imme ); Reg reg = cast(Reg, Byte[0] & Register::Mask_Right ); u16 immediate = width == Width_Byte ? Imme.Data.Low : Imme.Data; str_operand_left = Register::meumonic( reg, width ); str_operand_right = string_format( "%u", immediate ); } else { switch ( code ) { #pragma region mov case mov_RM_R: { length++; Width width = get_width( Mask_Width ); str_operand_left = Register::meumonic( mode_operand_left_reg(), width ); dissassemble_mode( str_operand_right, width, length ); } break; case mov_Im_RM: { Width width = get_width( Mask_Width_Imme ); Memory operand_left = cast( Memory, Byte[1] & Mask_Memory ); u16 address = EffAddr.Disp; u16 immediate = width == Width_Byte ? EffAddr.Data.Low : EffAddr.Data; dissassemble_mode( str_operand_left, width, length ); str_operand_right = string_format( "%u", immediate ); } break; case mov_M_Acc: { Width width = get_width( Mask_Width ); u16 address = width == Width_Byte ? Addr.Data.Low : Addr.Data; str_operand_left = Register::meumonic( Reg::AX, Width_Word ); str_operand_right = string_format( "%u", address ); } break; case mov_Acc_M: { Width width = get_width( Mask_Width ); u16 address = width == Width_Byte ? Addr.Data.Low : Addr.Data; str_operand_right = string_format( "%u", address ); str_operand_left = Register::meumonic( Reg::AX, Width_Word ); } break; case mov_RM_SR: { } break; case mov_SR_RM: { } break; #pragma endregion mov } } assembly = string_format( assembly, 32, "\n%s %s, %s" // opcode operand_right, operand_left , Op::meumonic( code) , str_operand_right , str_operand_left ); if ( result_out == nullptr ) return length; if ( * result_out == nullptr ) * result_out = string_make( zpl_kilobytes(1) ); * result_out = string_append( * result_out, assembly ); zpl_printf("\n\nAssembly: %s\n\n", assembly); return length; } }; namespace Tests { void try_mock_instruction() { using namespace Field; using namespace Op; using namespace Register; Instruction mock; // mov CX, BX mock.Byte[0] = mov_RM_R | Dir_Src | Field::Width_Word; mock.Byte[1] = Field::Mode_Reg | offset_left_reg(BX) | CX; zpl_printf("\n\nAttempting Mock Instruction: mov CX, BX"); print_nl(); print_as_binary( & mock.Byte[0], 1, " " ); print_as_binary( & mock.Byte[1], 1, " " ); print_nl(); zpl_string dissasembly = nullptr; mock.dissassemble( & dissasembly); } void try_blob_single_move() { zpl_printf("\n\nAttempting to read blob: listing_0037_single_register_mov"); zpl_file_contents blob = zpl_file_read_contents( g_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[0] = data[0]; instr.Byte[1] = data[1]; instr.dissassemble( & dissasembly); } void try_blob_many_moves() { zpl_printf("\n\nAttempting to read blob: listing_0038_many_register_mov"); zpl_file_contents blob = zpl_file_read_contents( g_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 = string_make( "bits 16\n"); while ( left ) { Instruction instr; instr.Byte[0] = data[0]; instr.Byte[1] = 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 ); } void try_blob_more_moves() { zpl_printf("\n\nAttempting to read blob: listing_0039_more_movs"); zpl_file_contents blob = zpl_file_read_contents( g_allocator, false, "tests/listing_0039_more_movs" ); 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 = string_make( "bits 16\n"); while ( left ) { Instruction instr; instr.Ptr = data; u8 length = instr.dissassemble( & dissasembly); data += length; left -= length; } 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_0039_more_movs.asm.out.asm" , dissasembly , zpl_string_length(dissasembly) , nullptr ); } } int main() { zpl_printf("sim 8086!"); setup(); f64 start = zpl_time_rel(); // Tests::try_blob_single_move(); // Tests::try_blob_many_moves(); Tests::try_blob_more_moves(); f64 end = zpl_time_rel(); f64 ms = (end - start) * 100; printf("\n\nElapsed Time: %lf ms", ms); printf("\n\n"); cleanup(); return 0; }