1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-24 12:30:17 +00:00

Merge pull request #1198 from TomHarte/DirectWrite

Add compiler assistance on access types
This commit is contained in:
Thomas Harte 2023-11-07 22:23:41 -05:00 committed by GitHub
commit 0fee3ff92c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 669 additions and 364 deletions

View File

@ -0,0 +1,66 @@
//
// AccessType.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef AccessType_h
#define AccessType_h
namespace InstructionSet::x86 {
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
/// the caller supplies as `MemoryT` and `RegistersT` when obtaining a reference to whatever the processor
/// intends to reference.
///
/// `perform` guarantees to validate all accesses before modifying any state, giving the caller opportunity to generate
/// any exceptions that might be applicable.
enum class AccessType {
/// The requested value will be read from.
Read,
/// The requested value will be written to.
Write,
/// The requested value will be read from and then written to.
ReadModifyWrite,
/// The requested value has already been authorised for whatever form of access is now intended, so there's no
/// need further to inspect. This is done e.g. by operations that will push multiple values to the stack to verify that
/// all necessary stack space is available ahead of pushing anything, though each individual push will then result in
/// a further `Preauthorised` access.
PreauthorisedRead,
};
constexpr bool is_writeable(AccessType type) {
return type == AccessType::ReadModifyWrite || type == AccessType::Write;
}
template <typename IntT, AccessType type> struct Accessor;
// Reads: return a value directly.
template <typename IntT> struct Accessor<IntT, AccessType::Read> { using type = IntT; };
template <typename IntT> struct Accessor<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
// Writes: return a custom type that can be written but not read.
template <typename IntT>
class Writeable {
public:
Writeable(IntT &target) : target_(target) {}
IntT operator=(IntT value) { return target_ = value; }
private:
IntT &target_;
};
template <typename IntT> struct Accessor<IntT, AccessType::Write> { using type = Writeable<IntT>; };
// Read-modify-writes: return a reference.
template <typename IntT> struct Accessor<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
// Shorthands; assumed that preauthorised reads have the same return type as reads.
template<typename IntT> using read_t = typename Accessor<IntT, AccessType::Read>::type;
template<typename IntT> using write_t = typename Accessor<IntT, AccessType::Write>::type;
template<typename IntT> using modify_t = typename Accessor<IntT, AccessType::ReadModifyWrite>::type;
template<typename IntT, AccessType type> using access_t = typename Accessor<IntT, type>::type;
}
#endif /* AccessType_h */

View File

@ -163,19 +163,19 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
PartialBlock(0x20, AND); break;
case 0x26: segment_override_ = Source::ES; break;
case 0x27: Complete(DAA, eAX, eAX, DataSize::Byte); break;
case 0x27: Complete(DAA, None, None, DataSize::Byte); break;
PartialBlock(0x28, SUB); break;
case 0x2e: segment_override_ = Source::CS; break;
case 0x2f: Complete(DAS, eAX, eAX, DataSize::Byte); break;
case 0x2f: Complete(DAS, None, None, DataSize::Byte); break;
PartialBlock(0x30, XOR); break;
case 0x36: segment_override_ = Source::SS; break;
case 0x37: Complete(AAA, eAX, eAX, DataSize::Word); break;
case 0x37: Complete(AAA, None, None, DataSize::Word); break;
PartialBlock(0x38, CMP); break;
case 0x3e: segment_override_ = Source::DS; break;
case 0x3f: Complete(AAS, eAX, eAX, DataSize::Word); break;
case 0x3f: Complete(AAS, None, None, DataSize::Word); break;
#undef PartialBlock
@ -361,8 +361,8 @@ std::pair<int, typename Decoder<model>::InstructionT> Decoder<model>::decode(con
case 0x96: Complete(XCHG, eAX, eSI, data_size_); break;
case 0x97: Complete(XCHG, eAX, eDI, data_size_); break;
case 0x98: Complete(CBW, eAX, AH, data_size_); break;
case 0x99: Complete(CWD, eAX, eDX, data_size_); break;
case 0x98: Complete(CBW, None, None, data_size_); break;
case 0x99: Complete(CWD, None, None, data_size_); break;
case 0x9a: Far(CALLfar); break;
case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break;
case 0x9c: Complete(PUSHF, None, None, data_size_); break;

View File

@ -13,180 +13,13 @@
#include "../../../Numeric/Carry.hpp"
#include "../../../Numeric/RegisterSizes.hpp"
#include "../Interrupts.hpp"
#include "../AccessType.hpp"
#include "Resolver.hpp"
#include <utility>
namespace InstructionSet::x86 {
template <typename IntT, AccessType access, typename InstructionT, typename ContextT>
IntT *resolve(
InstructionT &instruction,
Source source,
DataPointer pointer,
ContextT &context,
IntT *none = nullptr,
IntT *immediate = nullptr
);
template <Source source, typename IntT, AccessType access, typename InstructionT, typename ContextT>
uint32_t address(
InstructionT &instruction,
DataPointer pointer,
ContextT &context
) {
// TODO: non-word indexes and bases.
if constexpr (source == Source::DirectAddress) {
return instruction.offset();
}
uint32_t address;
uint16_t zero = 0;
address = *resolve<uint16_t, access>(instruction, pointer.index(), pointer, context, &zero);
if constexpr (is_32bit(ContextT::model)) {
address <<= pointer.scale();
}
address += instruction.offset();
if constexpr (source == Source::IndirectNoBase) {
return address;
}
return address + *resolve<uint16_t, access>(instruction, pointer.base(), pointer, context);
}
template <typename IntT, AccessType access, Source source, typename ContextT>
IntT *register_(ContextT &context) {
static constexpr bool supports_dword = is_32bit(ContextT::model);
switch(source) {
case Source::eAX:
// Slightly contorted if chain here and below:
//
// (i) does the `constexpr` version of a `switch`; and
// (i) ensures .eax() etc aren't called on @c registers for 16-bit processors, so they need not implement 32-bit storage.
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.eax(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.ax(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.al(); }
else { return nullptr; }
case Source::eCX:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.ecx(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.cx(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.cl(); }
else { return nullptr; }
case Source::eDX:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.edx(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.dx(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.dl(); }
else if constexpr (std::is_same_v<IntT, uint32_t>) { return nullptr; }
case Source::eBX:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.ebx(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.bx(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.bl(); }
else if constexpr (std::is_same_v<IntT, uint32_t>) { return nullptr; }
case Source::eSPorAH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.esp(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.sp(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.ah(); }
else { return nullptr; }
case Source::eBPorCH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.ebp(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.bp(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.ch(); }
else { return nullptr; }
case Source::eSIorDH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.esi(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.si(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.dh(); }
else { return nullptr; }
case Source::eDIorBH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.edi(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.di(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.bh(); }
else { return nullptr; }
default: return nullptr;
}
}
template <typename IntT, AccessType access, typename InstructionT, typename ContextT>
uint32_t address(
InstructionT &instruction,
DataPointer pointer,
ContextT &context
) {
// TODO: at least on the 8086 this isn't how register 'addresses' are resolved; instead whatever was the last computed address
// remains in the address register and is returned. Find out what other x86s do and make a decision.
switch(pointer.source()) {
default: return 0;
case Source::eAX: return *register_<IntT, access, Source::eAX>(context);
case Source::eCX: return *register_<IntT, access, Source::eCX>(context);
case Source::eDX: return *register_<IntT, access, Source::eDX>(context);
case Source::eBX: return *register_<IntT, access, Source::eBX>(context);
case Source::eSPorAH: return *register_<IntT, access, Source::eSPorAH>(context);
case Source::eBPorCH: return *register_<IntT, access, Source::eBPorCH>(context);
case Source::eSIorDH: return *register_<IntT, access, Source::eSIorDH>(context);
case Source::eDIorBH: return *register_<IntT, access, Source::eDIorBH>(context);
case Source::Indirect: return address<Source::Indirect, IntT, access>(instruction, pointer, context);
case Source::IndirectNoBase: return address<Source::IndirectNoBase, IntT, access>(instruction, pointer, context);
case Source::DirectAddress: return address<Source::DirectAddress, IntT, access>(instruction, pointer, context);
}
}
template <typename IntT, AccessType access, typename InstructionT, typename ContextT>
IntT *resolve(
InstructionT &instruction,
Source source,
DataPointer pointer,
ContextT &context,
IntT *none,
IntT *immediate
) {
// Rules:
//
// * if this is a memory access, set target_address and break;
// * otherwise return the appropriate value.
uint32_t target_address;
switch(source) {
case Source::eAX: return register_<IntT, access, Source::eAX>(context);
case Source::eCX: return register_<IntT, access, Source::eCX>(context);
case Source::eDX: return register_<IntT, access, Source::eDX>(context);
case Source::eBX: return register_<IntT, access, Source::eBX>(context);
case Source::eSPorAH: return register_<IntT, access, Source::eSPorAH>(context);
case Source::eBPorCH: return register_<IntT, access, Source::eBPorCH>(context);
case Source::eSIorDH: return register_<IntT, access, Source::eSIorDH>(context);
case Source::eDIorBH: return register_<IntT, access, Source::eDIorBH>(context);
// Segment registers are always 16-bit.
case Source::ES: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.es(); else return nullptr;
case Source::CS: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.cs(); else return nullptr;
case Source::SS: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.ss(); else return nullptr;
case Source::DS: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.ds(); else return nullptr;
// 16-bit models don't have FS and GS.
case Source::FS: if constexpr (is_32bit(ContextT::model) && std::is_same_v<IntT, uint16_t>) return &context.registers.fs(); else return nullptr;
case Source::GS: if constexpr (is_32bit(ContextT::model) && std::is_same_v<IntT, uint16_t>) return &context.registers.gs(); else return nullptr;
case Source::Immediate:
*immediate = instruction.operand();
return immediate;
case Source::None: return none;
case Source::Indirect:
target_address = address<Source::Indirect, IntT, access>(instruction, pointer, context);
break;
case Source::IndirectNoBase:
target_address = address<Source::IndirectNoBase, IntT, access>(instruction, pointer, context);
break;
case Source::DirectAddress:
target_address = address<Source::DirectAddress, IntT, access>(instruction, pointer, context);
break;
}
// If execution has reached here then a memory fetch is required.
// Do it and exit.
return &context.memory.template access<IntT, access>(instruction.data_segment(), target_address);
};
namespace Primitive {
// The below takes a reference in order properly to handle PUSH SP,
@ -194,9 +27,13 @@ namespace Primitive {
template <typename IntT, bool preauthorised, typename ContextT>
void push(IntT &value, ContextT &context) {
context.registers.sp_ -= sizeof(IntT);
context.memory.template access<IntT, preauthorised ? AccessType::PreauthorisedWrite : AccessType::Write>(
Source::SS,
context.registers.sp_) = value;
if constexpr (preauthorised) {
context.memory.template preauthorised_write<IntT>(Source::SS, context.registers.sp_, value);
} else {
context.memory.template access<IntT, AccessType::Write>(
Source::SS,
context.registers.sp_) = value;
}
context.memory.template write_back<IntT>();
}
@ -421,7 +258,11 @@ void das(uint8_t &al, ContextT &context) {
}
template <bool with_carry, typename IntT, typename ContextT>
void add(IntT &destination, IntT source, ContextT &context) {
void add(
modify_t<IntT> destination,
read_t<IntT> source,
ContextT &context
) {
/*
DEST DEST + SRC [+ CF];
*/
@ -442,8 +283,12 @@ void add(IntT &destination, IntT source, ContextT &context) {
destination = result;
}
template <bool with_borrow, bool write_back, typename IntT, typename ContextT>
void sub(IntT &destination, IntT source, ContextT &context) {
template <bool with_borrow, AccessType destination_type, typename IntT, typename ContextT>
void sub(
access_t<IntT, destination_type> destination,
read_t<IntT> source,
ContextT &context
) {
/*
DEST DEST - (SRC [+ CF]);
*/
@ -461,13 +306,17 @@ void sub(IntT &destination, IntT source, ContextT &context) {
context.flags.template set_from<IntT, Flag::Zero, Flag::Sign, Flag::ParityOdd>(result);
if constexpr (write_back) {
if constexpr (is_writeable(destination_type)) {
destination = result;
}
}
template <typename IntT, typename ContextT>
void test(IntT &destination, IntT source, ContextT &context) {
void test(
read_t<IntT> destination,
read_t<IntT> source,
ContextT &context
) {
/*
TEMP SRC1 AND SRC2;
SF MSB(TEMP);
@ -491,7 +340,10 @@ void test(IntT &destination, IntT source, ContextT &context) {
}
template <typename IntT>
void xchg(IntT &destination, IntT &source) {
void xchg(
modify_t<IntT> destination,
modify_t<IntT> source
) {
/*
TEMP DEST
DEST SRC
@ -501,7 +353,12 @@ void xchg(IntT &destination, IntT &source) {
}
template <typename IntT, typename ContextT>
void mul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) {
void mul(
modify_t<IntT> destination_high,
modify_t<IntT> destination_low,
read_t<IntT> source,
ContextT &context
) {
/*
IF byte operation
THEN
@ -523,7 +380,12 @@ void mul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &c
}
template <typename IntT, typename ContextT>
void imul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) {
void imul(
modify_t<IntT> destination_high,
modify_t<IntT> destination_low,
read_t<IntT> source,
ContextT &context
) {
/*
(as modified by https://www.felixcloutier.com/x86/daa ...)
@ -558,7 +420,12 @@ void imul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &
}
template <typename IntT, typename ContextT>
void div(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) {
void div(
modify_t<IntT> destination_high,
modify_t<IntT> destination_low,
read_t<IntT> source,
ContextT &context
) {
/*
IF SRC = 0
THEN #DE; (* divide error *)
@ -614,7 +481,12 @@ void div(IntT &destination_high, IntT &destination_low, IntT source, ContextT &c
}
template <typename IntT, typename ContextT>
void idiv(IntT &destination_high, IntT &destination_low, IntT source, ContextT &context) {
void idiv(
modify_t<IntT> destination_high,
modify_t<IntT> destination_low,
read_t<IntT> source,
ContextT &context
) {
/*
IF SRC = 0
THEN #DE; (* divide error *)
@ -671,7 +543,10 @@ void idiv(IntT &destination_high, IntT &destination_low, IntT source, ContextT &
}
template <typename IntT, typename ContextT>
void inc(IntT &destination, ContextT &context) {
void inc(
modify_t<IntT> destination,
ContextT &context
) {
/*
DEST DEST + 1;
*/
@ -687,7 +562,11 @@ void inc(IntT &destination, ContextT &context) {
}
template <typename IntT, typename ContextT>
void jump(bool condition, IntT displacement, ContextT &context) {
void jump(
bool condition,
IntT displacement,
ContextT &context
) {
/*
IF condition
THEN
@ -706,7 +585,11 @@ void jump(bool condition, IntT displacement, ContextT &context) {
}
template <typename IntT, typename OffsetT, typename ContextT>
void loop(IntT &counter, OffsetT displacement, ContextT &context) {
void loop(
modify_t<IntT> counter,
OffsetT displacement,
ContextT &context
) {
--counter;
if(counter) {
context.flow_controller.jump(context.registers.ip() + displacement);
@ -714,7 +597,11 @@ void loop(IntT &counter, OffsetT displacement, ContextT &context) {
}
template <typename IntT, typename OffsetT, typename ContextT>
void loope(IntT &counter, OffsetT displacement, ContextT &context) {
void loope(
modify_t<IntT> counter,
OffsetT displacement,
ContextT &context
) {
--counter;
if(counter && context.flags.template flag<Flag::Zero>()) {
context.flow_controller.jump(context.registers.ip() + displacement);
@ -722,7 +609,11 @@ void loope(IntT &counter, OffsetT displacement, ContextT &context) {
}
template <typename IntT, typename OffsetT, typename ContextT>
void loopne(IntT &counter, OffsetT displacement, ContextT &context) {
void loopne(
modify_t<IntT> counter,
OffsetT displacement,
ContextT &context
) {
--counter;
if(counter && !context.flags.template flag<Flag::Zero>()) {
context.flow_controller.jump(context.registers.ip() + displacement);
@ -730,7 +621,10 @@ void loopne(IntT &counter, OffsetT displacement, ContextT &context) {
}
template <typename IntT, typename ContextT>
void dec(IntT &destination, ContextT &context) {
void dec(
modify_t<IntT> destination,
ContextT &context
) {
/*
DEST DEST - 1;
*/
@ -747,7 +641,11 @@ void dec(IntT &destination, ContextT &context) {
}
template <typename IntT, typename ContextT>
void and_(IntT &destination, IntT source, ContextT &context) {
void and_(
modify_t<IntT> destination,
read_t<IntT> source,
ContextT &context
) {
/*
DEST DEST AND SRC;
*/
@ -762,7 +660,11 @@ void and_(IntT &destination, IntT source, ContextT &context) {
}
template <typename IntT, typename ContextT>
void or_(IntT &destination, IntT source, ContextT &context) {
void or_(
modify_t<IntT> destination,
read_t<IntT> source,
ContextT &context
) {
/*
DEST DEST OR SRC;
*/
@ -777,7 +679,11 @@ void or_(IntT &destination, IntT source, ContextT &context) {
}
template <typename IntT, typename ContextT>
void xor_(IntT &destination, IntT source, ContextT &context) {
void xor_(
modify_t<IntT> destination,
read_t<IntT> source,
ContextT &context
) {
/*
DEST DEST XOR SRC;
*/
@ -792,7 +698,10 @@ void xor_(IntT &destination, IntT source, ContextT &context) {
}
template <typename IntT, typename ContextT>
void neg(IntT &destination, ContextT &context) {
void neg(
modify_t<IntT> destination,
ContextT &context
) {
/*
IF DEST = 0
THEN CF 0
@ -814,35 +723,49 @@ void neg(IntT &destination, ContextT &context) {
}
template <typename IntT>
void not_(IntT &destination) {
void not_(
modify_t<IntT> destination
) {
/*
DEST NOT DEST;
*/
/*
Flags affected: none.
*/
destination = ~destination;
destination = ~destination;
}
template <typename IntT, typename ContextT>
void call_relative(IntT offset, ContextT &context) {
void call_relative(
IntT offset,
ContextT &context
) {
push<uint16_t, false>(context.registers.ip(), context);
context.flow_controller.jump(context.registers.ip() + offset);
}
template <typename IntT, typename ContextT>
void call_absolute(IntT target, ContextT &context) {
void call_absolute(
read_t<IntT> target,
ContextT &context
) {
push<uint16_t, false>(context.registers.ip(), context);
context.flow_controller.jump(target);
}
template <typename IntT, typename ContextT>
void jump_absolute(IntT target, ContextT &context) {
void jump_absolute(
read_t<IntT> target,
ContextT &context
) {
context.flow_controller.jump(target);
}
template <typename InstructionT, typename ContextT>
void call_far(InstructionT &instruction, ContextT &context) {
void call_far(
InstructionT &instruction,
ContextT &context
) {
// TODO: eliminate 16-bit assumption below.
const Source source_segment = instruction.data_segment();
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2);
@ -881,7 +804,10 @@ void call_far(InstructionT &instruction, ContextT &context) {
}
template <typename InstructionT, typename ContextT>
void jump_far(InstructionT &instruction, ContextT &context) {
void jump_far(
InstructionT &instruction,
ContextT &context
) {
// TODO: eliminate 16-bit assumption below.
uint16_t source_address = 0;
const auto pointer = instruction.destination();
@ -910,7 +836,9 @@ void jump_far(InstructionT &instruction, ContextT &context) {
}
template <typename ContextT>
void iret(ContextT &context) {
void iret(
ContextT &context
) {
// TODO: all modes other than 16-bit real mode.
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3);
const auto ip = pop<uint16_t, true>(context);
@ -920,14 +848,20 @@ void iret(ContextT &context) {
}
template <typename InstructionT, typename ContextT>
void ret_near(InstructionT instruction, ContextT &context) {
void ret_near(
InstructionT instruction,
ContextT &context
) {
const auto ip = pop<uint16_t, false>(context);
context.registers.sp() += instruction.operand();
context.flow_controller.jump(ip);
}
template <typename InstructionT, typename ContextT>
void ret_far(InstructionT instruction, ContextT &context) {
void ret_far(
InstructionT instruction,
ContextT &context
) {
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2);
const auto ip = pop<uint16_t, true>(context);
const auto cs = pop<uint16_t, true>(context);
@ -937,8 +871,8 @@ void ret_far(InstructionT instruction, ContextT &context) {
template <Source selector, typename InstructionT, typename ContextT>
void ld(
InstructionT &instruction,
uint16_t &destination,
const InstructionT &instruction,
write_t<uint16_t> destination,
ContextT &context
) {
const auto pointer = instruction.source();
@ -957,7 +891,7 @@ void ld(
template <typename IntT, typename InstructionT, typename ContextT>
void lea(
const InstructionT &instruction,
IntT &destination,
write_t<IntT> destination,
ContextT &context
) {
// TODO: address size.
@ -978,19 +912,27 @@ void xlat(
}
template <typename IntT>
void mov(IntT &destination, IntT source) {
void mov(
write_t<IntT> destination,
read_t<IntT> source
) {
destination = source;
}
template <typename ContextT>
void into(ContextT &context) {
void into(
ContextT &context
) {
if(context.flags.template flag<Flag::Overflow>()) {
interrupt(Interrupt::OnOverflow, context);
}
}
template <typename ContextT>
void sahf(uint8_t &ah, ContextT &context) {
void sahf(
uint8_t &ah,
ContextT &context
) {
/*
EFLAGS(SF:ZF:0:AF:0:PF:1:CF) AH;
*/
@ -1002,7 +944,10 @@ void sahf(uint8_t &ah, ContextT &context) {
}
template <typename ContextT>
void lahf(uint8_t &ah, ContextT &context) {
void lahf(
uint8_t &ah,
ContextT &context
) {
/*
AH EFLAGS(SF:ZF:0:AF:0:PF:1:CF);
*/
@ -1016,7 +961,9 @@ void lahf(uint8_t &ah, ContextT &context) {
}
template <typename IntT>
void cbw(IntT &ax) {
void cbw(
IntT &ax
) {
constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1);
constexpr IntT low_half = (1 << (sizeof(IntT) * 4)) - 1;
@ -1028,7 +975,10 @@ void cbw(IntT &ax) {
}
template <typename IntT>
void cwd(IntT &dx, IntT ax) {
void cwd(
IntT &dx,
IntT ax
) {
dx = ax & Numeric::top_bit<IntT>() ? IntT(~0) : IntT(0);
}
@ -1051,19 +1001,29 @@ void cmc(ContextT &context) {
}
template <typename ContextT>
void salc(uint8_t &al, ContextT &context) {
void salc(
uint8_t &al,
ContextT &context
) {
al = context.flags.template flag<Flag::Carry>() ? 0xff : 0x00;
}
template <typename IntT, typename ContextT>
void setmo(IntT &destination, ContextT &context) {
destination = ~0;
void setmo(
write_t<IntT> destination,
ContextT &context
) {
const auto result = destination = ~0;
context.flags.template set_from<Flag::Carry, Flag::AuxiliaryCarry, Flag::Overflow>(0);
context.flags.template set_from<IntT, Flag::Sign, Flag::Zero, Flag::ParityOdd>(destination);
context.flags.template set_from<IntT, Flag::Sign, Flag::Zero, Flag::ParityOdd>(result);
}
template <typename IntT, typename ContextT>
void rcl(IntT &destination, uint8_t count, ContextT &context) {
void rcl(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
/*
(* RCL and RCR instructions *)
SIZE OperandSize
@ -1119,7 +1079,11 @@ void rcl(IntT &destination, uint8_t count, ContextT &context) {
}
template <typename IntT, typename ContextT>
void rcr(IntT &destination, uint8_t count, ContextT &context) {
void rcr(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
/*
(* RCR instruction operation *)
IF COUNT = 1
@ -1161,7 +1125,11 @@ void rcr(IntT &destination, uint8_t count, ContextT &context) {
}
template <typename IntT, typename ContextT>
void rol(IntT &destination, uint8_t count, ContextT &context) {
void rol(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
/*
(* ROL and ROR instructions *)
SIZE OperandSize
@ -1208,7 +1176,11 @@ void rol(IntT &destination, uint8_t count, ContextT &context) {
}
template <typename IntT, typename ContextT>
void ror(IntT &destination, uint8_t count, ContextT &context) {
void ror(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
/*
(* ROL and ROR instructions *)
SIZE OperandSize
@ -1311,7 +1283,11 @@ void ror(IntT &destination, uint8_t count, ContextT &context) {
For a non-zero count, the AF flag is undefined.
*/
template <typename IntT, typename ContextT>
void sal(IntT &destination, uint8_t count, ContextT &context) {
void sal(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
switch(count) {
case 0: return;
case Numeric::bit_size<IntT>():
@ -1338,7 +1314,11 @@ void sal(IntT &destination, uint8_t count, ContextT &context) {
}
template <typename IntT, typename ContextT>
void sar(IntT &destination, uint8_t count, ContextT &context) {
void sar(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
if(!count) {
return;
}
@ -1357,7 +1337,11 @@ void sar(IntT &destination, uint8_t count, ContextT &context) {
}
template <typename IntT, typename ContextT>
void shr(IntT &destination, uint8_t count, ContextT &context) {
void shr(
modify_t<IntT> destination,
uint8_t count,
ContextT &context
) {
if(!count) {
return;
}
@ -1378,23 +1362,32 @@ void shr(IntT &destination, uint8_t count, ContextT &context) {
}
template <typename ContextT>
void popf(ContextT &context) {
void popf(
ContextT &context
) {
context.flags.set(pop<uint16_t, false>(context));
}
template <typename ContextT>
void pushf(ContextT &context) {
void pushf(
ContextT &context
) {
uint16_t value = context.flags.get();
push<uint16_t, false>(value, context);
}
template <typename AddressT, Repetition repetition>
bool repetition_over(const AddressT &eCX) {
bool repetition_over(
const AddressT &eCX
) {
return repetition != Repetition::None && !eCX;
}
template <typename AddressT, Repetition repetition, typename ContextT>
void repeat(AddressT &eCX, ContextT &context) {
void repeat(
AddressT &eCX,
ContextT &context
) {
if(
repetition == Repetition::None || // No repetition => stop.
!(--eCX) // [e]cx is zero after being decremented => stop.
@ -1411,7 +1404,13 @@ void repeat(AddressT &eCX, ContextT &context) {
}
template <typename IntT, typename AddressT, Repetition repetition, typename InstructionT, typename ContextT>
void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, ContextT &context) {
void cmps(
const InstructionT &instruction,
AddressT &eCX,
AddressT &eSI,
AddressT &eDI,
ContextT &context
) {
if(repetition_over<AddressT, repetition>(eCX)) {
return;
}
@ -1421,7 +1420,7 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address
eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
Primitive::sub<false, false>(lhs, rhs, context);
Primitive::sub<false, AccessType::Read, IntT>(lhs, rhs, context);
repeat<AddressT, repetition>(eCX, context);
}
@ -1435,7 +1434,7 @@ void scas(AddressT &eCX, AddressT &eDI, IntT &eAX, ContextT &context) {
const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT);
Primitive::sub<false, false>(eAX, rhs, context);
Primitive::sub<false, AccessType::Read, IntT>(eAX, rhs, context);
repeat<AddressT, repetition>(eCX, context);
}
@ -1537,8 +1536,8 @@ template <
//
// (though GCC offers C++20 syntax as an extension, and Clang seems to follow along, so maybe I'm overthinking)
IntT immediate;
const auto source_r = [&]() -> IntT& {
return *resolve<IntT, AccessType::Read>(
const auto source_r = [&]() -> read_t<IntT> {
return resolve<IntT, AccessType::Read>(
instruction,
instruction.source().source(),
instruction.source(),
@ -1546,8 +1545,8 @@ template <
nullptr,
&immediate);
};
const auto source_rmw = [&]() -> IntT& {
return *resolve<IntT, AccessType::ReadModifyWrite>(
const auto source_rmw = [&]() -> modify_t<IntT> {
return resolve<IntT, AccessType::ReadModifyWrite>(
instruction,
instruction.source().source(),
instruction.source(),
@ -1555,8 +1554,8 @@ template <
nullptr,
&immediate);
};
const auto destination_r = [&]() -> IntT& {
return *resolve<IntT, AccessType::Read>(
const auto destination_r = [&]() -> read_t<IntT> {
return resolve<IntT, AccessType::Read>(
instruction,
instruction.destination().source(),
instruction.destination(),
@ -1564,8 +1563,8 @@ template <
nullptr,
&immediate);
};
const auto destination_w = [&]() -> IntT& {
return *resolve<IntT, AccessType::Write>(
const auto destination_w = [&]() -> write_t<IntT> {
return resolve<IntT, AccessType::Write>(
instruction,
instruction.destination().source(),
instruction.destination(),
@ -1573,8 +1572,8 @@ template <
nullptr,
&immediate);
};
const auto destination_rmw = [&]() -> IntT& {
return *resolve<IntT, AccessType::ReadModifyWrite>(
const auto destination_rmw = [&]() -> modify_t<IntT> {
return resolve<IntT, AccessType::ReadModifyWrite>(
instruction,
instruction.destination().source(),
instruction.destination(),
@ -1672,39 +1671,45 @@ template <
case Operation::HLT: context.flow_controller.halt(); return;
case Operation::WAIT: context.flow_controller.wait(); return;
case Operation::ADC: Primitive::add<true>(destination_rmw(), source_r(), context); break;
case Operation::ADD: Primitive::add<false>(destination_rmw(), source_r(), context); break;
case Operation::SBB: Primitive::sub<true, true>(destination_rmw(), source_r(), context); break;
case Operation::SUB: Primitive::sub<false, true>(destination_rmw(), source_r(), context); break;
case Operation::CMP: Primitive::sub<false, false>(destination_r(), source_r(), context); return;
case Operation::TEST: Primitive::test(destination_r(), source_r(), context); return;
case Operation::ADC: Primitive::add<true, IntT>(destination_rmw(), source_r(), context); break;
case Operation::ADD: Primitive::add<false, IntT>(destination_rmw(), source_r(), context); break;
case Operation::SBB:
Primitive::sub<true, AccessType::ReadModifyWrite, IntT>(destination_rmw(), source_r(), context);
break;
case Operation::SUB:
Primitive::sub<false, AccessType::ReadModifyWrite, IntT>(destination_rmw(), source_r(), context);
break;
case Operation::CMP:
Primitive::sub<false, AccessType::Read, IntT>(destination_r(), source_r(), context);
return;
case Operation::TEST: Primitive::test<IntT>(destination_r(), source_r(), context); return;
case Operation::MUL: Primitive::mul(pair_high(), pair_low(), source_r(), context); return;
case Operation::IMUL_1: Primitive::imul(pair_high(), pair_low(), source_r(), context); return;
case Operation::DIV: Primitive::div(pair_high(), pair_low(), source_r(), context); return;
case Operation::IDIV: Primitive::idiv(pair_high(), pair_low(), source_r(), context); return;
case Operation::MUL: Primitive::mul<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::IMUL_1: Primitive::imul<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::DIV: Primitive::div<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::IDIV: Primitive::idiv<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::INC: Primitive::inc(destination_rmw(), context); break;
case Operation::DEC: Primitive::dec(destination_rmw(), context); break;
case Operation::INC: Primitive::inc<IntT>(destination_rmw(), context); break;
case Operation::DEC: Primitive::dec<IntT>(destination_rmw(), context); break;
case Operation::AND: Primitive::and_(destination_rmw(), source_r(), context); break;
case Operation::OR: Primitive::or_(destination_rmw(), source_r(), context); break;
case Operation::XOR: Primitive::xor_(destination_rmw(), source_r(), context); break;
case Operation::NEG: Primitive::neg(source_rmw(), context); break; // TODO: should be a destination.
case Operation::NOT: Primitive::not_(source_rmw()); break; // TODO: should be a destination.
case Operation::AND: Primitive::and_<IntT>(destination_rmw(), source_r(), context); break;
case Operation::OR: Primitive::or_<IntT>(destination_rmw(), source_r(), context); break;
case Operation::XOR: Primitive::xor_<IntT>(destination_rmw(), source_r(), context); break;
case Operation::NEG: Primitive::neg<IntT>(source_rmw(), context); break; // TODO: should be a destination.
case Operation::NOT: Primitive::not_<IntT>(source_rmw()); break; // TODO: should be a destination.
case Operation::CALLrel: Primitive::call_relative(instruction.displacement(), context); return;
case Operation::CALLabs: Primitive::call_absolute(destination_r(), context); return;
case Operation::CALLfar: Primitive::call_far(instruction, context); return;
case Operation::CALLrel: Primitive::call_relative<AddressT>(instruction.displacement(), context); return;
case Operation::CALLabs: Primitive::call_absolute<IntT>(destination_r(), context); return;
case Operation::CALLfar: Primitive::call_far(instruction, context); return;
case Operation::JMPrel: jcc(true); return;
case Operation::JMPabs: Primitive::jump_absolute(destination_r(), context); return;
case Operation::JMPfar: Primitive::jump_far(instruction, context); return;
case Operation::JMPrel: jcc(true); return;
case Operation::JMPabs: Primitive::jump_absolute<IntT>(destination_r(), context); return;
case Operation::JMPfar: Primitive::jump_far(instruction, context); return;
case Operation::JCXZ: jcc(!eCX()); return;
case Operation::LOOP: Primitive::loop(eCX(), instruction.offset(), context); return;
case Operation::LOOPE: Primitive::loope(eCX(), instruction.offset(), context); return;
case Operation::LOOPNE: Primitive::loopne(eCX(), instruction.offset(), context); return;
case Operation::JCXZ: jcc(!eCX()); return;
case Operation::LOOP: Primitive::loop<AddressT>(eCX(), instruction.offset(), context); return;
case Operation::LOOPE: Primitive::loope<AddressT>(eCX(), instruction.offset(), context); return;
case Operation::LOOPNE: Primitive::loopne<AddressT>(eCX(), instruction.offset(), context); return;
case Operation::IRET: Primitive::iret(context); return;
case Operation::RETnear: Primitive::ret_near(instruction, context); return;
@ -1719,33 +1724,33 @@ template <
case Operation::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld<Source::DS>(instruction, destination_w(), context); return;
case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld<Source::ES>(instruction, destination_w(), context); return;
case Operation::LEA: Primitive::lea(instruction, destination_w(), context); return;
case Operation::MOV: Primitive::mov(destination_w(), source_r()); break;
case Operation::LEA: Primitive::lea<IntT>(instruction, destination_w(), context); return;
case Operation::MOV: Primitive::mov<IntT>(destination_w(), source_r()); break;
case Operation::JO: jcc(context.flags.template condition<Condition::Overflow>()); return;
case Operation::JNO: jcc(!context.flags.template condition<Condition::Overflow>()); return;
case Operation::JB: jcc(context.flags.template condition<Condition::Below>()); return;
case Operation::JNB: jcc(!context.flags.template condition<Condition::Below>()); return;
case Operation::JNB: jcc(!context.flags.template condition<Condition::Below>()); return;
case Operation::JZ: jcc(context.flags.template condition<Condition::Zero>()); return;
case Operation::JNZ: jcc(!context.flags.template condition<Condition::Zero>()); return;
case Operation::JBE: jcc(context.flags.template condition<Condition::BelowOrEqual>()); return;
case Operation::JNBE: jcc(!context.flags.template condition<Condition::BelowOrEqual>()); return;
case Operation::JS: jcc(context.flags.template condition<Condition::Sign>()); return;
case Operation::JNS: jcc(!context.flags.template condition<Condition::Sign>()); return;
case Operation::JP: jcc(!context.flags.template condition<Condition::ParityOdd>()); return;
case Operation::JP: jcc(!context.flags.template condition<Condition::ParityOdd>()); return;
case Operation::JNP: jcc(context.flags.template condition<Condition::ParityOdd>()); return;
case Operation::JL: jcc(context.flags.template condition<Condition::Less>()); return;
case Operation::JNL: jcc(!context.flags.template condition<Condition::Less>()); return;
case Operation::JLE: jcc(context.flags.template condition<Condition::LessOrEqual>()); return;
case Operation::JNLE: jcc(!context.flags.template condition<Condition::LessOrEqual>()); return;
case Operation::RCL: Primitive::rcl(destination_rmw(), shift_count(), context); break;
case Operation::RCR: Primitive::rcr(destination_rmw(), shift_count(), context); break;
case Operation::ROL: Primitive::rol(destination_rmw(), shift_count(), context); break;
case Operation::ROR: Primitive::ror(destination_rmw(), shift_count(), context); break;
case Operation::SAL: Primitive::sal(destination_rmw(), shift_count(), context); break;
case Operation::SAR: Primitive::sar(destination_rmw(), shift_count(), context); break;
case Operation::SHR: Primitive::shr(destination_rmw(), shift_count(), context); break;
case Operation::RCL: Primitive::rcl<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::RCR: Primitive::rcr<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::ROL: Primitive::rol<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::ROR: Primitive::ror<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::SAL: Primitive::sal<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::SAR: Primitive::sar<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::SHR: Primitive::shr<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::CLC: Primitive::clc(context); return;
case Operation::CLD: Primitive::cld(context); return;
@ -1755,12 +1760,12 @@ template <
case Operation::STI: Primitive::sti(context); return;
case Operation::CMC: Primitive::cmc(context); return;
case Operation::XCHG: Primitive::xchg(destination_rmw(), source_rmw()); break;
case Operation::XCHG: Primitive::xchg<IntT>(destination_rmw(), source_rmw()); break;
case Operation::SALC: Primitive::salc(context.registers.al(), context); return;
case Operation::SALC: Primitive::salc(context.registers.al(), context); return;
case Operation::SETMO:
if constexpr (ContextT::model == Model::i8086) {
Primitive::setmo(destination_w(), context);
Primitive::setmo<IntT>(destination_w(), context);
break;
} else {
// TODO.
@ -1771,7 +1776,7 @@ template <
// Test CL out here to avoid taking a reference to memory if
// no write is going to occur.
if(context.registers.cl()) {
Primitive::setmo(destination_w(), context);
Primitive::setmo<IntT>(destination_w(), context);
}
break;
} else {
@ -1785,7 +1790,10 @@ template <
case Operation::XLAT: Primitive::xlat<AddressT>(instruction, context); return;
case Operation::POP: destination_w() = Primitive::pop<IntT, false>(context); break;
case Operation::PUSH: Primitive::push<IntT, false>(source_r(), context); break;
case Operation::PUSH:
Primitive::push<IntT, false>(source_rmw(), context); // PUSH SP modifies SP before pushing it;
// hence PUSH is sometimes read-modify-write.
break;
case Operation::POPF: Primitive::popf(context); break;
case Operation::PUSHF: Primitive::pushf(context); break;

View File

@ -0,0 +1,207 @@
//
// Resolver.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/11/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef Resolver_h
#define Resolver_h
#include "../AccessType.hpp"
namespace InstructionSet::x86 {
/// Obtain a pointer to the value desribed by @c source, which is one of those named by @c pointer, using @c instruction and @c context
/// for offsets, registers and memory contents.
///
/// If @c source is Source::None then @c none is returned.
///
/// If @c source is Source::Immediate then the appropriate portion of @c instrucion's operand
/// is copied to @c *immediate and @c immediate is returned.
template <typename IntT, AccessType access, typename InstructionT, typename ContextT>
typename Accessor<IntT, access>::type resolve(
InstructionT &instruction,
Source source,
DataPointer pointer,
ContextT &context,
IntT *none = nullptr,
IntT *immediate = nullptr
);
/// Calculates the absolute address for @c pointer given the registers and memory provided in @c context and taking any
/// referenced offset from @c instruction.
template <Source source, typename IntT, AccessType access, typename InstructionT, typename ContextT>
uint32_t address(
InstructionT &instruction,
DataPointer pointer,
ContextT &context
) {
if constexpr (source == Source::DirectAddress) {
return instruction.offset();
}
uint32_t address;
uint16_t zero = 0;
address = resolve<uint16_t, AccessType::Read>(instruction, pointer.index(), pointer, context, &zero);
if constexpr (is_32bit(ContextT::model)) {
address <<= pointer.scale();
}
address += instruction.offset();
if constexpr (source == Source::IndirectNoBase) {
return address;
}
return address + resolve<uint16_t, AccessType::Read>(instruction, pointer.base(), pointer, context);
}
/// @returns a pointer to the contents of the register identified by the combination of @c IntT and @c Source if any;
/// @c nullptr otherwise. @c access is currently unused but is intended to provide the hook upon which updates to
/// segment registers can be tracked for protected modes.
template <typename IntT, AccessType access, Source source, typename ContextT>
IntT *register_(ContextT &context) {
static constexpr bool supports_dword = is_32bit(ContextT::model);
switch(source) {
case Source::eAX:
// Slightly contorted if chain here and below:
//
// (i) does the `constexpr` version of a `switch`; and
// (i) ensures .eax() etc aren't called on @c registers for 16-bit processors, so they need not implement 32-bit storage.
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.eax(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.ax(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.al(); }
else { return nullptr; }
case Source::eCX:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.ecx(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.cx(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.cl(); }
else { return nullptr; }
case Source::eDX:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.edx(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.dx(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.dl(); }
else if constexpr (std::is_same_v<IntT, uint32_t>) { return nullptr; }
case Source::eBX:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.ebx(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.bx(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.bl(); }
else if constexpr (std::is_same_v<IntT, uint32_t>) { return nullptr; }
case Source::eSPorAH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.esp(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.sp(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.ah(); }
else { return nullptr; }
case Source::eBPorCH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.ebp(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.bp(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.ch(); }
else { return nullptr; }
case Source::eSIorDH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.esi(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.si(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.dh(); }
else { return nullptr; }
case Source::eDIorBH:
if constexpr (supports_dword && std::is_same_v<IntT, uint32_t>) { return &context.registers.edi(); }
else if constexpr (std::is_same_v<IntT, uint16_t>) { return &context.registers.di(); }
else if constexpr (std::is_same_v<IntT, uint8_t>) { return &context.registers.bh(); }
else { return nullptr; }
// Segment registers are always 16-bit.
case Source::ES: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.es(); else return nullptr;
case Source::CS: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.cs(); else return nullptr;
case Source::SS: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.ss(); else return nullptr;
case Source::DS: if constexpr (std::is_same_v<IntT, uint16_t>) return &context.registers.ds(); else return nullptr;
// 16-bit models don't have FS and GS.
case Source::FS: if constexpr (is_32bit(ContextT::model) && std::is_same_v<IntT, uint16_t>) return &context.registers.fs(); else return nullptr;
case Source::GS: if constexpr (is_32bit(ContextT::model) && std::is_same_v<IntT, uint16_t>) return &context.registers.gs(); else return nullptr;
default: return nullptr;
}
}
///Obtains the address described by @c pointer from @c instruction given the registers and memory as described by @c context.
template <typename IntT, AccessType access, typename InstructionT, typename ContextT>
uint32_t address(
InstructionT &instruction,
DataPointer pointer,
ContextT &context
) {
// TODO: at least on the 8086 this isn't how register 'addresses' are resolved; instead whatever was the last computed address
// remains in the address register and is returned. Find out what other x86s do and make a decision.
switch(pointer.source()) {
default: return 0;
case Source::eAX: return *register_<IntT, access, Source::eAX>(context);
case Source::eCX: return *register_<IntT, access, Source::eCX>(context);
case Source::eDX: return *register_<IntT, access, Source::eDX>(context);
case Source::eBX: return *register_<IntT, access, Source::eBX>(context);
case Source::eSPorAH: return *register_<IntT, access, Source::eSPorAH>(context);
case Source::eBPorCH: return *register_<IntT, access, Source::eBPorCH>(context);
case Source::eSIorDH: return *register_<IntT, access, Source::eSIorDH>(context);
case Source::eDIorBH: return *register_<IntT, access, Source::eDIorBH>(context);
case Source::Indirect: return address<Source::Indirect, IntT, access>(instruction, pointer, context);
case Source::IndirectNoBase: return address<Source::IndirectNoBase, IntT, access>(instruction, pointer, context);
case Source::DirectAddress: return address<Source::DirectAddress, IntT, access>(instruction, pointer, context);
}
}
// See forward declaration, above, for details.
template <typename IntT, AccessType access, typename InstructionT, typename ContextT>
typename Accessor<IntT, access>::type resolve(
InstructionT &instruction,
Source source,
DataPointer pointer,
ContextT &context,
IntT *none,
IntT *immediate
) {
// Rules:
//
// * if this is a memory access, set target_address and break;
// * otherwise return the appropriate value.
uint32_t target_address;
switch(source) {
// Defer all register accesses to the register-specific lookup.
case Source::eAX: return *register_<IntT, access, Source::eAX>(context);
case Source::eCX: return *register_<IntT, access, Source::eCX>(context);
case Source::eDX: return *register_<IntT, access, Source::eDX>(context);
case Source::eBX: return *register_<IntT, access, Source::eBX>(context);
case Source::eSPorAH: return *register_<IntT, access, Source::eSPorAH>(context);
case Source::eBPorCH: return *register_<IntT, access, Source::eBPorCH>(context);
case Source::eSIorDH: return *register_<IntT, access, Source::eSIorDH>(context);
case Source::eDIorBH: return *register_<IntT, access, Source::eDIorBH>(context);
case Source::ES: return *register_<IntT, access, Source::ES>(context);
case Source::CS: return *register_<IntT, access, Source::CS>(context);
case Source::SS: return *register_<IntT, access, Source::SS>(context);
case Source::DS: return *register_<IntT, access, Source::DS>(context);
case Source::FS: return *register_<IntT, access, Source::FS>(context);
case Source::GS: return *register_<IntT, access, Source::GS>(context);
case Source::None: return *none;
case Source::Immediate:
*immediate = instruction.operand();
return *immediate;
case Source::Indirect:
target_address = address<Source::Indirect, IntT, access>(instruction, pointer, context);
break;
case Source::IndirectNoBase:
target_address = address<Source::IndirectNoBase, IntT, access>(instruction, pointer, context);
break;
case Source::DirectAddress:
target_address = address<Source::DirectAddress, IntT, access>(instruction, pointer, context);
break;
}
// If execution has reached here then a memory fetch is required.
// Do it and exit.
return context.memory.template access<IntT, access>(instruction.data_segment(), target_address);
}
}
#endif /* Resolver_h */

View File

@ -506,7 +506,12 @@ std::string InstructionSet::x86::to_string(
default: {
const int operands = max_displayed_operands(instruction.second.operation());
const bool displacement = has_displacement(instruction.second.operation());
const bool print_first = operands > 1 && instruction.second.destination().source() != Source::None;
const bool print_first =
instruction.second.destination().source() != Source::None &&
(
operands > 1 ||
(operands > 0 && instruction.second.source().source() == Source::None)
);
if(print_first) {
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
}

View File

@ -15,27 +15,6 @@
namespace InstructionSet::x86 {
/// Explains the type of access that `perform` intends to perform; is provided as a template parameter to whatever
/// the caller supplies as `MemoryT` and `RegistersT` when obtaining a reference to whatever the processor
/// intends to reference.
///
/// `perform` guarantees to validate all accesses before modifying any state, giving the caller opportunity to generate
/// any exceptions that might be applicable.
enum class AccessType {
/// The requested value will be read from.
Read,
/// The requested value will be written to.
Write,
/// The requested value will be read from and then written to.
ReadModifyWrite,
/// The requested value has already been authorised for whatever form of access is now intended, so there's no
/// need further to inspect. This is done e.g. by operations that will push multiple values to the stack to verify that
/// all necessary stack space is available ahead of pushing anything, though each individual push will then result in
/// a further `Preauthorised` access.
PreauthorisedRead,
PreauthorisedWrite,
};
template <
Model model_,
typename FlowControllerT,

View File

@ -1150,6 +1150,8 @@
42A5E8412ABBE16F00A0DD5D /* nop_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = nop_test.bin; sourceTree = "<group>"; };
42A5E8422ABBE16F00A0DD5D /* lax_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = lax_test.bin; sourceTree = "<group>"; };
42A5E8432ABBE16F00A0DD5D /* branch_backwards_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = branch_backwards_test.bin; sourceTree = "<group>"; };
42AA41232AF888370016751C /* AccessType.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AccessType.hpp; sourceTree = "<group>"; };
42AA41242AF8893F0016751C /* Resolver.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Resolver.hpp; sourceTree = "<group>"; };
42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = "<group>"; };
42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = "<group>"; };
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
@ -2328,6 +2330,7 @@
isa = PBXGroup;
children = (
42437B382ACF2798006DFED1 /* PerformImplementation.hpp */,
42AA41242AF8893F0016751C /* Resolver.hpp */,
);
path = Implementation;
sourceTree = "<group>";
@ -4997,12 +5000,13 @@
children = (
4BEDA3B925B25563000C2DBD /* Decoder.cpp */,
4B69DEB52AB79E4F0055B217 /* Instruction.cpp */,
42AA41232AF888370016751C /* AccessType.hpp */,
4BEDA3B825B25563000C2DBD /* Decoder.hpp */,
42437B342ACF02A9006DFED1 /* Flags.hpp */,
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */,
42437B392AD07465006DFED1 /* Interrupts.hpp */,
4BE3C69527CBC540000EAD28 /* Model.hpp */,
42437B352ACF0AA2006DFED1 /* Perform.hpp */,
42437B342ACF02A9006DFED1 /* Flags.hpp */,
42437B372ACF2798006DFED1 /* Implementation */,
);
path = x86;

View File

@ -19,6 +19,7 @@
#include "NSData+dataWithContentsOfGZippedFile.h"
#include "../../../InstructionSets/x86/AccessType.hpp"
#include "../../../InstructionSets/x86/Decoder.hpp"
#include "../../../InstructionSets/x86/Perform.hpp"
#include "../../../InstructionSets/x86/Flags.hpp"
@ -97,17 +98,6 @@ struct Memory {
public:
using AccessType = InstructionSet::x86::AccessType;
template <typename IntT, AccessType type> struct ReturnType;
// Reads: return a value directly.
template <typename IntT> struct ReturnType<IntT, AccessType::Read> { using type = IntT; };
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedRead> { using type = IntT; };
// Writes: return a reference.
template <typename IntT> struct ReturnType<IntT, AccessType::Write> { using type = IntT &; };
template <typename IntT> struct ReturnType<IntT, AccessType::ReadModifyWrite> { using type = IntT &; };
template <typename IntT> struct ReturnType<IntT, AccessType::PreauthorisedWrite> { using type = IntT &; };
// Constructor.
Memory(Registers &registers) : registers_(registers) {
memory.resize(1024*1024);
@ -127,7 +117,9 @@ struct Memory {
tags[address] = Tag::AccessExpected;
}
//
// Preauthorisation call-ins.
//
void preauthorise_stack_write(uint32_t length) {
uint16_t sp = registers_.sp_;
while(length--) {
@ -155,34 +147,19 @@ struct Memory {
}
}
//
// Access call-ins.
//
// Accesses an address based on segment:offset.
template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint32_t address) {
if constexpr (std::is_same_v<IntT, uint16_t>) {
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
// to the start. So the 16-bit value will need to be a local cache.
if(address == 0xffff) {
write_back_address_[0] = (segment_base(segment) + address) & 0xf'ffff;
write_back_address_[1] = (write_back_address_[0] - 65535) & 0xf'ffff;
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
return write_back_value_;
}
}
auto &value = access<IntT, type>(segment, address, Tag::Accessed);
// If the CPU has indicated a write, it should be safe to fuzz the value now.
if(type == AccessType::Write) {
value = IntT(~0);
}
return value;
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
return access<IntT, type>(segment, offset, Tag::Accessed);
}
// Accesses an address based on physical location.
template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(uint32_t address) {
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address) {
return access<IntT, type>(address, Tag::Accessed);
}
@ -197,6 +174,41 @@ struct Memory {
}
}
//
// Direct write.
//
template <typename IntT>
void preauthorised_write(InstructionSet::x86::Source segment, uint16_t offset, IntT value) {
if(!test_preauthorisation(address(segment, offset))) {
printf("Non-preauthorised access\n");
}
// Bytes can be written without further ado.
if constexpr (std::is_same_v<IntT, uint8_t>) {
memory[address(segment, offset) & 0xf'ffff] = value;
return;
}
// Words that straddle the segment end must be split in two.
if(offset == 0xffff) {
memory[address(segment, offset) & 0xf'ffff] = value & 0xff;
memory[address(segment, 0x0000) & 0xf'ffff] = value >> 8;
return;
}
const uint32_t target = address(segment, offset) & 0xf'ffff;
// Words that straddle the end of physical RAM must also be split in two.
if(target == 0xf'ffff) {
memory[0xf'ffff] = value & 0xff;
memory[0x0'0000] = value >> 8;
return;
}
// It's safe just to write then.
*reinterpret_cast<uint16_t *>(&memory[target]) = value;
}
private:
enum class Tag {
Seeded,
@ -236,46 +248,70 @@ struct Memory {
return physical_address << 4;
}
uint32_t address(InstructionSet::x86::Source segment, uint16_t offset) {
return (segment_base(segment) + offset) & 0xf'ffff;
}
// Entry point used by the flow controller so that it can mark up locations at which the flags were written,
// so that defined-flag-only masks can be applied while verifying RAM contents.
template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) {
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff;
typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) {
const uint32_t physical_address = address(segment, offset);
if constexpr (std::is_same_v<IntT, uint16_t>) {
// If this is a 16-bit access that runs past the end of the segment, it'll wrap back
// to the start. So the 16-bit value will need to be a local cache.
if(offset == 0xffff) {
return split_word<type>(physical_address, address(segment, 0), tag);
}
}
return access<IntT, type>(physical_address, tag);
}
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative
// to a selector, they're just at an absolute location.
template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(uint32_t address, Tag tag) {
if constexpr (type == AccessType::PreauthorisedRead || type == AccessType::PreauthorisedWrite) {
typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address, Tag tag) {
if constexpr (type == AccessType::PreauthorisedRead) {
if(!test_preauthorisation(address)) {
printf("Non preauthorised access\n");
}
}
// Check for address wraparound
if(address > 0x10'0000 - sizeof(IntT)) {
if constexpr (std::is_same_v<IntT, uint8_t>) {
address &= 0xf'ffff;
} else {
address &= 0xf'ffff;
if(address == 0xf'ffff) {
// This is a 16-bit access comprising the final byte in memory and the first.
write_back_address_[0] = address;
write_back_address_[1] = 0;
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
return write_back_value_;
}
}
for(size_t c = 0; c < sizeof(IntT); c++) {
tags[(address + c) & 0xf'ffff] = tag;
}
if(tags.find(address) == tags.end()) {
printf("Access to unexpected RAM address\n");
// Dispense with the single-byte case trivially.
if constexpr (std::is_same_v<IntT, uint8_t>) {
return memory[address];
} else if(address != 0xf'ffff) {
return *reinterpret_cast<IntT *>(&memory[address]);
} else {
return split_word<type>(address, 0, tag);
}
}
template <AccessType type>
typename InstructionSet::x86::Accessor<uint16_t, type>::type
split_word(uint32_t low_address, uint32_t high_address, Tag tag) {
if constexpr (is_writeable(type)) {
write_back_address_[0] = low_address;
write_back_address_[1] = high_address;
tags[low_address] = tag;
tags[high_address] = tag;
// Prepopulate only if this is a modify.
if constexpr (type == AccessType::ReadModifyWrite) {
write_back_value_ = memory[write_back_address_[0]] | (memory[write_back_address_[1]] << 8);
}
return write_back_value_;
} else {
return memory[low_address] | (memory[high_address] << 8);
}
tags[address] = tag;
return *reinterpret_cast<IntT *>(&memory[address]);
}
static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.