// // DataPointerResolver.hpp // Clock Signal // // Created by Thomas Harte on 24/02/2022. // Copyright © 2022 Thomas Harte. All rights reserved. // #ifndef DataPointerResolver_hpp #define DataPointerResolver_hpp #include "Instruction.hpp" #include "Model.hpp" #include namespace InstructionSet::x86 { /// Unlike source, describes only registers, and breaks /// them down by conventional name — so AL, AH, AX and EAX are all /// listed separately and uniquely, rather than being eAX+size or /// eSPorAH with a size of 1. enum class Register: uint8_t { // 8-bit registers. AL, AH, CL, CH, DL, DH, BL, BH, // 16-bit registers. AX, CX, DX, BX, SP, BP, SI, DI, ES, CS, SS, DS, FS, GS, // 32-bit registers. EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, // None }; /// @returns @c true if @c r is the same size as @c DataT; @c false otherwise. /// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both /// working then it shouldn't be necessary to test this in register files. template constexpr bool is_sized(Register r) { static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1); if constexpr (sizeof(DataT) == 4) { return r >= Register::EAX && r < Register::None; } if constexpr (sizeof(DataT) == 2) { return r >= Register::AX && r < Register::EAX; } if constexpr (sizeof(DataT) == 1) { return r >= Register::AL && r < Register::AX; } return false; } /// @returns the proper @c Register given @c source and data of size @c sizeof(DataT), /// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS). template constexpr Register register_for_source(Source source) { static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1); if constexpr (sizeof(DataT) == 4) { switch(source) { case Source::eAX: return Register::EAX; case Source::eCX: return Register::ECX; case Source::eDX: return Register::EDX; case Source::eBX: return Register::EBX; case Source::eSPorAH: return Register::ESP; case Source::eBPorCH: return Register::EBP; case Source::eSIorDH: return Register::ESI; case Source::eDIorBH: return Register::EDI; default: break; } } if constexpr (sizeof(DataT) == 2) { switch(source) { case Source::eAX: return Register::AX; case Source::eCX: return Register::CX; case Source::eDX: return Register::DX; case Source::eBX: return Register::BX; case Source::eSPorAH: return Register::SP; case Source::eBPorCH: return Register::BP; case Source::eSIorDH: return Register::SI; case Source::eDIorBH: return Register::DI; case Source::ES: return Register::ES; case Source::CS: return Register::CS; case Source::SS: return Register::SS; case Source::DS: return Register::DS; case Source::FS: return Register::FS; case Source::GS: return Register::GS; default: break; } } if constexpr (sizeof(DataT) == 1) { switch(source) { case Source::eAX: return Register::AL; case Source::eCX: return Register::CL; case Source::eDX: return Register::DL; case Source::eBX: return Register::BL; case Source::eSPorAH: return Register::AH; case Source::eBPorCH: return Register::CH; case Source::eSIorDH: return Register::DH; case Source::eDIorBH: return Register::BH; default: break; } } return Register::None; } /// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes: /// /// * a register bank; and /// * a memory pool. /// /// The register bank should implement `template DataT read()` and `template void write(DataT)`. /// Those functions will be called only with registers and data types that are appropriate to the @c model. /// /// The memory pool should implement `template DataT read(Source segment, uint32_t address)` and /// `template void write(Source segment, uint32_t address, DataT value)`. template class DataPointerResolver { public: public: /// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary. template static DataT read( RegistersT ®isters, MemoryT &memory, const Instruction &instruction, DataPointer pointer); /// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary. template static void write( RegistersT ®isters, MemoryT &memory, const Instruction &instruction, DataPointer pointer, DataT value); /// Computes the effective address of @c pointer including any displacement applied by @c instruction. /// @c pointer must be of type Source::Indirect. template static uint32_t effective_address( RegistersT ®isters, const Instruction &instruction, DataPointer pointer); private: template static void access( RegistersT ®isters, MemoryT &memory, const Instruction &instruction, DataPointer pointer, DataT &value); }; // // Implementation begins here. // template template DataT DataPointerResolver::read( RegistersT ®isters, MemoryT &memory, const Instruction &instruction, DataPointer pointer) { DataT result; access(registers, memory, instruction, pointer, result); return result; } template template void DataPointerResolver::write( RegistersT ®isters, MemoryT &memory, const Instruction &instruction, DataPointer pointer, DataT value) { access(registers, memory, instruction, pointer, value); } #define rw(v, r, is_write) \ case Source::r: \ using VType = typename std::remove_reference::type; \ if constexpr (is_write) { \ registers.template write(Source::r)>(v); \ } else { \ v = registers.template read(Source::r)>(); \ } \ break; #define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \ rw(v, eDX, i); rw(v, eBX, i); \ rw(v, eSPorAH, i); rw(v, eBPorCH, i); \ rw(v, eSIorDH, i); rw(v, eDIorBH, i); \ rw(v, ES, i); rw(v, CS, i); \ rw(v, SS, i); rw(v, DS, i); \ rw(v, FS, i); rw(v, GS, i); template template uint32_t DataPointerResolver::effective_address( RegistersT ®isters, const Instruction &instruction, DataPointer pointer) { using AddressT = typename Instruction::AddressT; AddressT base = 0, index = 0; if constexpr (has_base) { switch(pointer.base()) { default: break; ALLREGS(base, false); } } switch(pointer.index()) { default: break; ALLREGS(index, false); } uint32_t address = index; if constexpr (model >= Model::i80386) { address <<= pointer.scale(); } else { assert(!pointer.scale()); } // Always compute address as 32-bit. // TODO: verify use of memory_mask around here. // Also I think possibly an exception is supposed to be generated // if the programmer is in 32-bit mode and has asked for 16-bit // address computation but generated e.g. a 17-bit result. Look into // that when working on execution. For now the goal is merely decoding // and this code exists both to verify the presence of all necessary // fields and to help to explore the best breakdown of storage // within Instruction. constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff}; const uint32_t memory_mask = memory_masks[int(instruction.address_size())]; address = (address & memory_mask) + (base & memory_mask) + instruction.displacement(); return address; } template template void DataPointerResolver::access( RegistersT ®isters, MemoryT &memory, const Instruction &instruction, DataPointer pointer, DataT &value) { const Source source = pointer.source(); switch(source) { default: if constexpr (!is_write) { value = 0; } return; ALLREGS(value, is_write); case Source::DirectAddress: if constexpr(is_write) { memory.template write(instruction.data_segment(), instruction.displacement(), value); } else { value = memory.template read(instruction.data_segment(), instruction.displacement()); } break; case Source::Immediate: value = DataT(instruction.operand()); break; #define indirect(has_base) { \ const auto address = effective_address \ (registers, instruction, pointer); \ \ if constexpr (is_write) { \ memory.template write( \ instruction.data_segment(), \ address, \ value \ ); \ } else { \ value = memory.template read( \ instruction.data_segment(), \ address \ ); \ } \ } case Source::IndirectNoBase: indirect(false); break; case Source::Indirect: indirect(true); break; #undef indirect } } #undef ALLREGS #undef rw } #endif /* DataPointerResolver_hpp */