1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-25 03:32:01 +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; PartialBlock(0x20, AND); break;
case 0x26: segment_override_ = Source::ES; 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; PartialBlock(0x28, SUB); break;
case 0x2e: segment_override_ = Source::CS; 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; PartialBlock(0x30, XOR); break;
case 0x36: segment_override_ = Source::SS; 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; PartialBlock(0x38, CMP); break;
case 0x3e: segment_override_ = Source::DS; 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 #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 0x96: Complete(XCHG, eAX, eSI, data_size_); break;
case 0x97: Complete(XCHG, eAX, eDI, data_size_); break; case 0x97: Complete(XCHG, eAX, eDI, data_size_); break;
case 0x98: Complete(CBW, eAX, AH, data_size_); break; case 0x98: Complete(CBW, None, None, data_size_); break;
case 0x99: Complete(CWD, eAX, eDX, data_size_); break; case 0x99: Complete(CWD, None, None, data_size_); break;
case 0x9a: Far(CALLfar); break; case 0x9a: Far(CALLfar); break;
case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break; case 0x9b: Complete(WAIT, None, None, DataSize::Byte); break;
case 0x9c: Complete(PUSHF, None, None, data_size_); break; case 0x9c: Complete(PUSHF, None, None, data_size_); break;

View File

@ -13,180 +13,13 @@
#include "../../../Numeric/Carry.hpp" #include "../../../Numeric/Carry.hpp"
#include "../../../Numeric/RegisterSizes.hpp" #include "../../../Numeric/RegisterSizes.hpp"
#include "../Interrupts.hpp" #include "../Interrupts.hpp"
#include "../AccessType.hpp"
#include "Resolver.hpp"
#include <utility> #include <utility>
namespace InstructionSet::x86 { 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 { namespace Primitive {
// The below takes a reference in order properly to handle PUSH SP, // 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> template <typename IntT, bool preauthorised, typename ContextT>
void push(IntT &value, ContextT &context) { void push(IntT &value, ContextT &context) {
context.registers.sp_ -= sizeof(IntT); context.registers.sp_ -= sizeof(IntT);
context.memory.template access<IntT, preauthorised ? AccessType::PreauthorisedWrite : AccessType::Write>( if constexpr (preauthorised) {
Source::SS, context.memory.template preauthorised_write<IntT>(Source::SS, context.registers.sp_, value);
context.registers.sp_) = value; } else {
context.memory.template access<IntT, AccessType::Write>(
Source::SS,
context.registers.sp_) = value;
}
context.memory.template write_back<IntT>(); 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> 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]; DEST DEST + SRC [+ CF];
*/ */
@ -442,8 +283,12 @@ void add(IntT &destination, IntT source, ContextT &context) {
destination = result; destination = result;
} }
template <bool with_borrow, bool write_back, typename IntT, typename ContextT> template <bool with_borrow, AccessType destination_type, typename IntT, typename ContextT>
void sub(IntT &destination, IntT source, ContextT &context) { void sub(
access_t<IntT, destination_type> destination,
read_t<IntT> source,
ContextT &context
) {
/* /*
DEST DEST - (SRC [+ CF]); 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); 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; destination = result;
} }
} }
template <typename IntT, typename ContextT> 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; TEMP SRC1 AND SRC2;
SF MSB(TEMP); SF MSB(TEMP);
@ -491,7 +340,10 @@ void test(IntT &destination, IntT source, ContextT &context) {
} }
template <typename IntT> template <typename IntT>
void xchg(IntT &destination, IntT &source) { void xchg(
modify_t<IntT> destination,
modify_t<IntT> source
) {
/* /*
TEMP DEST TEMP DEST
DEST SRC DEST SRC
@ -501,7 +353,12 @@ void xchg(IntT &destination, IntT &source) {
} }
template <typename IntT, typename ContextT> 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 IF byte operation
THEN THEN
@ -523,7 +380,12 @@ void mul(IntT &destination_high, IntT &destination_low, IntT source, ContextT &c
} }
template <typename IntT, typename ContextT> 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 ...) (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> 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 IF SRC = 0
THEN #DE; (* divide error *) 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> 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 IF SRC = 0
THEN #DE; (* divide error *) THEN #DE; (* divide error *)
@ -671,7 +543,10 @@ void idiv(IntT &destination_high, IntT &destination_low, IntT source, ContextT &
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
void inc(IntT &destination, ContextT &context) { void inc(
modify_t<IntT> destination,
ContextT &context
) {
/* /*
DEST DEST + 1; DEST DEST + 1;
*/ */
@ -687,7 +562,11 @@ void inc(IntT &destination, ContextT &context) {
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
void jump(bool condition, IntT displacement, ContextT &context) { void jump(
bool condition,
IntT displacement,
ContextT &context
) {
/* /*
IF condition IF condition
THEN THEN
@ -706,7 +585,11 @@ void jump(bool condition, IntT displacement, ContextT &context) {
} }
template <typename IntT, typename OffsetT, typename ContextT> 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; --counter;
if(counter) { if(counter) {
context.flow_controller.jump(context.registers.ip() + displacement); 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> 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; --counter;
if(counter && context.flags.template flag<Flag::Zero>()) { if(counter && context.flags.template flag<Flag::Zero>()) {
context.flow_controller.jump(context.registers.ip() + displacement); 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> 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; --counter;
if(counter && !context.flags.template flag<Flag::Zero>()) { if(counter && !context.flags.template flag<Flag::Zero>()) {
context.flow_controller.jump(context.registers.ip() + displacement); 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> template <typename IntT, typename ContextT>
void dec(IntT &destination, ContextT &context) { void dec(
modify_t<IntT> destination,
ContextT &context
) {
/* /*
DEST DEST - 1; DEST DEST - 1;
*/ */
@ -747,7 +641,11 @@ void dec(IntT &destination, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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; DEST DEST AND SRC;
*/ */
@ -762,7 +660,11 @@ void and_(IntT &destination, IntT source, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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; DEST DEST OR SRC;
*/ */
@ -777,7 +679,11 @@ void or_(IntT &destination, IntT source, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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; DEST DEST XOR SRC;
*/ */
@ -792,7 +698,10 @@ void xor_(IntT &destination, IntT source, ContextT &context) {
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
void neg(IntT &destination, ContextT &context) { void neg(
modify_t<IntT> destination,
ContextT &context
) {
/* /*
IF DEST = 0 IF DEST = 0
THEN CF 0 THEN CF 0
@ -814,35 +723,49 @@ void neg(IntT &destination, ContextT &context) {
} }
template <typename IntT> template <typename IntT>
void not_(IntT &destination) { void not_(
modify_t<IntT> destination
) {
/* /*
DEST NOT DEST; DEST NOT DEST;
*/ */
/* /*
Flags affected: none. Flags affected: none.
*/ */
destination = ~destination; destination = ~destination;
} }
template <typename IntT, typename ContextT> 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); push<uint16_t, false>(context.registers.ip(), context);
context.flow_controller.jump(context.registers.ip() + offset); context.flow_controller.jump(context.registers.ip() + offset);
} }
template <typename IntT, typename ContextT> 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); push<uint16_t, false>(context.registers.ip(), context);
context.flow_controller.jump(target); context.flow_controller.jump(target);
} }
template <typename IntT, typename ContextT> 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); context.flow_controller.jump(target);
} }
template <typename InstructionT, typename ContextT> 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. // TODO: eliminate 16-bit assumption below.
const Source source_segment = instruction.data_segment(); const Source source_segment = instruction.data_segment();
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2); 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> 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. // TODO: eliminate 16-bit assumption below.
uint16_t source_address = 0; uint16_t source_address = 0;
const auto pointer = instruction.destination(); const auto pointer = instruction.destination();
@ -910,7 +836,9 @@ void jump_far(InstructionT &instruction, ContextT &context) {
} }
template <typename ContextT> template <typename ContextT>
void iret(ContextT &context) { void iret(
ContextT &context
) {
// TODO: all modes other than 16-bit real mode. // TODO: all modes other than 16-bit real mode.
context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3); context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3);
const auto ip = pop<uint16_t, true>(context); const auto ip = pop<uint16_t, true>(context);
@ -920,14 +848,20 @@ void iret(ContextT &context) {
} }
template <typename InstructionT, typename ContextT> 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); const auto ip = pop<uint16_t, false>(context);
context.registers.sp() += instruction.operand(); context.registers.sp() += instruction.operand();
context.flow_controller.jump(ip); context.flow_controller.jump(ip);
} }
template <typename InstructionT, typename ContextT> 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); context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2);
const auto ip = pop<uint16_t, true>(context); const auto ip = pop<uint16_t, true>(context);
const auto cs = 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> template <Source selector, typename InstructionT, typename ContextT>
void ld( void ld(
InstructionT &instruction, const InstructionT &instruction,
uint16_t &destination, write_t<uint16_t> destination,
ContextT &context ContextT &context
) { ) {
const auto pointer = instruction.source(); const auto pointer = instruction.source();
@ -957,7 +891,7 @@ void ld(
template <typename IntT, typename InstructionT, typename ContextT> template <typename IntT, typename InstructionT, typename ContextT>
void lea( void lea(
const InstructionT &instruction, const InstructionT &instruction,
IntT &destination, write_t<IntT> destination,
ContextT &context ContextT &context
) { ) {
// TODO: address size. // TODO: address size.
@ -978,19 +912,27 @@ void xlat(
} }
template <typename IntT> template <typename IntT>
void mov(IntT &destination, IntT source) { void mov(
write_t<IntT> destination,
read_t<IntT> source
) {
destination = source; destination = source;
} }
template <typename ContextT> template <typename ContextT>
void into(ContextT &context) { void into(
ContextT &context
) {
if(context.flags.template flag<Flag::Overflow>()) { if(context.flags.template flag<Flag::Overflow>()) {
interrupt(Interrupt::OnOverflow, context); interrupt(Interrupt::OnOverflow, context);
} }
} }
template <typename ContextT> 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; EFLAGS(SF:ZF:0:AF:0:PF:1:CF) AH;
*/ */
@ -1002,7 +944,10 @@ void sahf(uint8_t &ah, ContextT &context) {
} }
template <typename ContextT> 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); AH EFLAGS(SF:ZF:0:AF:0:PF:1:CF);
*/ */
@ -1016,7 +961,9 @@ void lahf(uint8_t &ah, ContextT &context) {
} }
template <typename IntT> template <typename IntT>
void cbw(IntT &ax) { void cbw(
IntT &ax
) {
constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1);
constexpr IntT low_half = (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> 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); dx = ax & Numeric::top_bit<IntT>() ? IntT(~0) : IntT(0);
} }
@ -1051,19 +1001,29 @@ void cmc(ContextT &context) {
} }
template <typename ContextT> 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; al = context.flags.template flag<Flag::Carry>() ? 0xff : 0x00;
} }
template <typename IntT, typename ContextT> template <typename IntT, typename ContextT>
void setmo(IntT &destination, ContextT &context) { void setmo(
destination = ~0; 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<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> 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 *) (* RCL and RCR instructions *)
SIZE OperandSize SIZE OperandSize
@ -1119,7 +1079,11 @@ void rcl(IntT &destination, uint8_t count, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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 *) (* RCR instruction operation *)
IF COUNT = 1 IF COUNT = 1
@ -1161,7 +1125,11 @@ void rcr(IntT &destination, uint8_t count, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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 *) (* ROL and ROR instructions *)
SIZE OperandSize SIZE OperandSize
@ -1208,7 +1176,11 @@ void rol(IntT &destination, uint8_t count, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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 *) (* ROL and ROR instructions *)
SIZE OperandSize 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. For a non-zero count, the AF flag is undefined.
*/ */
template <typename IntT, typename ContextT> 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) { switch(count) {
case 0: return; case 0: return;
case Numeric::bit_size<IntT>(): case Numeric::bit_size<IntT>():
@ -1338,7 +1314,11 @@ void sal(IntT &destination, uint8_t count, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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) { if(!count) {
return; return;
} }
@ -1357,7 +1337,11 @@ void sar(IntT &destination, uint8_t count, ContextT &context) {
} }
template <typename IntT, typename ContextT> 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) { if(!count) {
return; return;
} }
@ -1378,23 +1362,32 @@ void shr(IntT &destination, uint8_t count, ContextT &context) {
} }
template <typename ContextT> template <typename ContextT>
void popf(ContextT &context) { void popf(
ContextT &context
) {
context.flags.set(pop<uint16_t, false>(context)); context.flags.set(pop<uint16_t, false>(context));
} }
template <typename ContextT> template <typename ContextT>
void pushf(ContextT &context) { void pushf(
ContextT &context
) {
uint16_t value = context.flags.get(); uint16_t value = context.flags.get();
push<uint16_t, false>(value, context); push<uint16_t, false>(value, context);
} }
template <typename AddressT, Repetition repetition> template <typename AddressT, Repetition repetition>
bool repetition_over(const AddressT &eCX) { bool repetition_over(
const AddressT &eCX
) {
return repetition != Repetition::None && !eCX; return repetition != Repetition::None && !eCX;
} }
template <typename AddressT, Repetition repetition, typename ContextT> template <typename AddressT, Repetition repetition, typename ContextT>
void repeat(AddressT &eCX, ContextT &context) { void repeat(
AddressT &eCX,
ContextT &context
) {
if( if(
repetition == Repetition::None || // No repetition => stop. repetition == Repetition::None || // No repetition => stop.
!(--eCX) // [e]cx is zero after being decremented => 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> 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)) { if(repetition_over<AddressT, repetition>(eCX)) {
return; return;
} }
@ -1421,7 +1420,7 @@ void cmps(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, Address
eSI += context.flags.template direction<AddressT>() * sizeof(IntT); eSI += context.flags.template direction<AddressT>() * sizeof(IntT);
eDI += 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); 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); const IntT rhs = context.memory.template access<IntT, AccessType::Read>(Source::ES, eDI);
eDI += context.flags.template direction<AddressT>() * sizeof(IntT); 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); 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) // (though GCC offers C++20 syntax as an extension, and Clang seems to follow along, so maybe I'm overthinking)
IntT immediate; IntT immediate;
const auto source_r = [&]() -> IntT& { const auto source_r = [&]() -> read_t<IntT> {
return *resolve<IntT, AccessType::Read>( return resolve<IntT, AccessType::Read>(
instruction, instruction,
instruction.source().source(), instruction.source().source(),
instruction.source(), instruction.source(),
@ -1546,8 +1545,8 @@ template <
nullptr, nullptr,
&immediate); &immediate);
}; };
const auto source_rmw = [&]() -> IntT& { const auto source_rmw = [&]() -> modify_t<IntT> {
return *resolve<IntT, AccessType::ReadModifyWrite>( return resolve<IntT, AccessType::ReadModifyWrite>(
instruction, instruction,
instruction.source().source(), instruction.source().source(),
instruction.source(), instruction.source(),
@ -1555,8 +1554,8 @@ template <
nullptr, nullptr,
&immediate); &immediate);
}; };
const auto destination_r = [&]() -> IntT& { const auto destination_r = [&]() -> read_t<IntT> {
return *resolve<IntT, AccessType::Read>( return resolve<IntT, AccessType::Read>(
instruction, instruction,
instruction.destination().source(), instruction.destination().source(),
instruction.destination(), instruction.destination(),
@ -1564,8 +1563,8 @@ template <
nullptr, nullptr,
&immediate); &immediate);
}; };
const auto destination_w = [&]() -> IntT& { const auto destination_w = [&]() -> write_t<IntT> {
return *resolve<IntT, AccessType::Write>( return resolve<IntT, AccessType::Write>(
instruction, instruction,
instruction.destination().source(), instruction.destination().source(),
instruction.destination(), instruction.destination(),
@ -1573,8 +1572,8 @@ template <
nullptr, nullptr,
&immediate); &immediate);
}; };
const auto destination_rmw = [&]() -> IntT& { const auto destination_rmw = [&]() -> modify_t<IntT> {
return *resolve<IntT, AccessType::ReadModifyWrite>( return resolve<IntT, AccessType::ReadModifyWrite>(
instruction, instruction,
instruction.destination().source(), instruction.destination().source(),
instruction.destination(), instruction.destination(),
@ -1672,39 +1671,45 @@ template <
case Operation::HLT: context.flow_controller.halt(); return; case Operation::HLT: context.flow_controller.halt(); return;
case Operation::WAIT: context.flow_controller.wait(); return; case Operation::WAIT: context.flow_controller.wait(); return;
case Operation::ADC: Primitive::add<true>(destination_rmw(), source_r(), context); break; case Operation::ADC: Primitive::add<true, IntT>(destination_rmw(), source_r(), context); break;
case Operation::ADD: Primitive::add<false>(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, true>(destination_rmw(), source_r(), context); break; case Operation::SBB:
case Operation::SUB: Primitive::sub<false, true>(destination_rmw(), source_r(), context); break; Primitive::sub<true, AccessType::ReadModifyWrite, IntT>(destination_rmw(), source_r(), context);
case Operation::CMP: Primitive::sub<false, false>(destination_r(), source_r(), context); return; break;
case Operation::TEST: Primitive::test(destination_r(), source_r(), context); return; 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::MUL: Primitive::mul<IntT>(pair_high(), pair_low(), source_r(), context); return;
case Operation::IMUL_1: Primitive::imul(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(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(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::INC: Primitive::inc<IntT>(destination_rmw(), context); break;
case Operation::DEC: Primitive::dec(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::AND: Primitive::and_<IntT>(destination_rmw(), source_r(), context); break;
case Operation::OR: Primitive::or_(destination_rmw(), source_r(), context); break; case Operation::OR: Primitive::or_<IntT>(destination_rmw(), source_r(), context); break;
case Operation::XOR: Primitive::xor_(destination_rmw(), source_r(), context); break; case Operation::XOR: Primitive::xor_<IntT>(destination_rmw(), source_r(), context); break;
case Operation::NEG: Primitive::neg(source_rmw(), context); break; // TODO: should be a destination. case Operation::NEG: Primitive::neg<IntT>(source_rmw(), context); break; // TODO: should be a destination.
case Operation::NOT: Primitive::not_(source_rmw()); 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::CALLrel: Primitive::call_relative<AddressT>(instruction.displacement(), context); return;
case Operation::CALLabs: Primitive::call_absolute(destination_r(), context); return; case Operation::CALLabs: Primitive::call_absolute<IntT>(destination_r(), context); return;
case Operation::CALLfar: Primitive::call_far(instruction, context); return; case Operation::CALLfar: Primitive::call_far(instruction, context); return;
case Operation::JMPrel: jcc(true); return; case Operation::JMPrel: jcc(true); return;
case Operation::JMPabs: Primitive::jump_absolute(destination_r(), context); return; case Operation::JMPabs: Primitive::jump_absolute<IntT>(destination_r(), context); return;
case Operation::JMPfar: Primitive::jump_far(instruction, context); return; case Operation::JMPfar: Primitive::jump_far(instruction, context); return;
case Operation::JCXZ: jcc(!eCX()); return; case Operation::JCXZ: jcc(!eCX()); return;
case Operation::LOOP: Primitive::loop(eCX(), instruction.offset(), context); return; case Operation::LOOP: Primitive::loop<AddressT>(eCX(), instruction.offset(), context); return;
case Operation::LOOPE: Primitive::loope(eCX(), instruction.offset(), context); return; case Operation::LOOPE: Primitive::loope<AddressT>(eCX(), instruction.offset(), context); return;
case Operation::LOOPNE: Primitive::loopne(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::IRET: Primitive::iret(context); return;
case Operation::RETnear: Primitive::ret_near(instruction, 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::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::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::LEA: Primitive::lea<IntT>(instruction, destination_w(), context); return;
case Operation::MOV: Primitive::mov(destination_w(), source_r()); break; case Operation::MOV: Primitive::mov<IntT>(destination_w(), source_r()); break;
case Operation::JO: jcc(context.flags.template condition<Condition::Overflow>()); return; case Operation::JO: jcc(context.flags.template condition<Condition::Overflow>()); return;
case Operation::JNO: 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::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::JZ: jcc(context.flags.template condition<Condition::Zero>()); return;
case Operation::JNZ: 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::JBE: jcc(context.flags.template condition<Condition::BelowOrEqual>()); return;
case Operation::JNBE: 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::JS: jcc(context.flags.template condition<Condition::Sign>()); return;
case Operation::JNS: 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::JNP: jcc(context.flags.template condition<Condition::ParityOdd>()); return;
case Operation::JL: jcc(context.flags.template condition<Condition::Less>()); 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::JNL: jcc(!context.flags.template condition<Condition::Less>()); return;
case Operation::JLE: jcc(context.flags.template condition<Condition::LessOrEqual>()); 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::JNLE: jcc(!context.flags.template condition<Condition::LessOrEqual>()); return;
case Operation::RCL: Primitive::rcl(destination_rmw(), shift_count(), context); break; case Operation::RCL: Primitive::rcl<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::RCR: Primitive::rcr(destination_rmw(), shift_count(), context); break; case Operation::RCR: Primitive::rcr<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::ROL: Primitive::rol(destination_rmw(), shift_count(), context); break; case Operation::ROL: Primitive::rol<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::ROR: Primitive::ror(destination_rmw(), shift_count(), context); break; case Operation::ROR: Primitive::ror<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::SAL: Primitive::sal(destination_rmw(), shift_count(), context); break; case Operation::SAL: Primitive::sal<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::SAR: Primitive::sar(destination_rmw(), shift_count(), context); break; case Operation::SAR: Primitive::sar<IntT>(destination_rmw(), shift_count(), context); break;
case Operation::SHR: Primitive::shr(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::CLC: Primitive::clc(context); return;
case Operation::CLD: Primitive::cld(context); return; case Operation::CLD: Primitive::cld(context); return;
@ -1755,12 +1760,12 @@ template <
case Operation::STI: Primitive::sti(context); return; case Operation::STI: Primitive::sti(context); return;
case Operation::CMC: Primitive::cmc(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: case Operation::SETMO:
if constexpr (ContextT::model == Model::i8086) { if constexpr (ContextT::model == Model::i8086) {
Primitive::setmo(destination_w(), context); Primitive::setmo<IntT>(destination_w(), context);
break; break;
} else { } else {
// TODO. // TODO.
@ -1771,7 +1776,7 @@ template <
// Test CL out here to avoid taking a reference to memory if // Test CL out here to avoid taking a reference to memory if
// no write is going to occur. // no write is going to occur.
if(context.registers.cl()) { if(context.registers.cl()) {
Primitive::setmo(destination_w(), context); Primitive::setmo<IntT>(destination_w(), context);
} }
break; break;
} else { } else {
@ -1785,7 +1790,10 @@ template <
case Operation::XLAT: Primitive::xlat<AddressT>(instruction, context); return; case Operation::XLAT: Primitive::xlat<AddressT>(instruction, context); return;
case Operation::POP: destination_w() = Primitive::pop<IntT, false>(context); break; 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::POPF: Primitive::popf(context); break;
case Operation::PUSHF: Primitive::pushf(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: { default: {
const int operands = max_displayed_operands(instruction.second.operation()); const int operands = max_displayed_operands(instruction.second.operation());
const bool displacement = has_displacement(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) { if(print_first) {
operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length); operation += to_string(instruction.second.destination(), instruction.second, offset_length, immediate_length);
} }

View File

@ -15,27 +15,6 @@
namespace InstructionSet::x86 { 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 < template <
Model model_, Model model_,
typename FlowControllerT, typename FlowControllerT,

View File

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

View File

@ -19,6 +19,7 @@
#include "NSData+dataWithContentsOfGZippedFile.h" #include "NSData+dataWithContentsOfGZippedFile.h"
#include "../../../InstructionSets/x86/AccessType.hpp"
#include "../../../InstructionSets/x86/Decoder.hpp" #include "../../../InstructionSets/x86/Decoder.hpp"
#include "../../../InstructionSets/x86/Perform.hpp" #include "../../../InstructionSets/x86/Perform.hpp"
#include "../../../InstructionSets/x86/Flags.hpp" #include "../../../InstructionSets/x86/Flags.hpp"
@ -97,17 +98,6 @@ struct Memory {
public: public:
using AccessType = InstructionSet::x86::AccessType; 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. // Constructor.
Memory(Registers &registers) : registers_(registers) { Memory(Registers &registers) : registers_(registers) {
memory.resize(1024*1024); memory.resize(1024*1024);
@ -127,7 +117,9 @@ struct Memory {
tags[address] = Tag::AccessExpected; tags[address] = Tag::AccessExpected;
} }
//
// Preauthorisation call-ins. // Preauthorisation call-ins.
//
void preauthorise_stack_write(uint32_t length) { void preauthorise_stack_write(uint32_t length) {
uint16_t sp = registers_.sp_; uint16_t sp = registers_.sp_;
while(length--) { while(length--) {
@ -155,34 +147,19 @@ struct Memory {
} }
} }
//
// Access call-ins. // Access call-ins.
//
// Accesses an address based on segment:offset. // Accesses an address based on segment:offset.
template <typename IntT, AccessType type> template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint32_t address) { typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset) {
if constexpr (std::is_same_v<IntT, uint16_t>) { return access<IntT, type>(segment, offset, Tag::Accessed);
// 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;
} }
// Accesses an address based on physical location. // Accesses an address based on physical location.
template <typename IntT, AccessType type> 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); 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: private:
enum class Tag { enum class Tag {
Seeded, Seeded,
@ -236,46 +248,70 @@ struct Memory {
return physical_address << 4; 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, // 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. // so that defined-flag-only masks can be applied while verifying RAM contents.
template <typename IntT, AccessType type> template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(InstructionSet::x86::Source segment, uint16_t address, Tag tag) { typename InstructionSet::x86::Accessor<IntT, type>::type access(InstructionSet::x86::Source segment, uint16_t offset, Tag tag) {
const uint32_t physical_address = (segment_base(segment) + address) & 0xf'ffff; 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); return access<IntT, type>(physical_address, tag);
} }
// An additional entry point for the flow controller; on the original 8086 interrupt vectors aren't relative // 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. // to a selector, they're just at an absolute location.
template <typename IntT, AccessType type> template <typename IntT, AccessType type>
typename ReturnType<IntT, type>::type &access(uint32_t address, Tag tag) { typename InstructionSet::x86::Accessor<IntT, type>::type access(uint32_t address, Tag tag) {
if constexpr (type == AccessType::PreauthorisedRead || type == AccessType::PreauthorisedWrite) { if constexpr (type == AccessType::PreauthorisedRead) {
if(!test_preauthorisation(address)) { if(!test_preauthorisation(address)) {
printf("Non preauthorised access\n"); printf("Non preauthorised access\n");
} }
} }
// Check for address wraparound for(size_t c = 0; c < sizeof(IntT); c++) {
if(address > 0x10'0000 - sizeof(IntT)) { tags[(address + c) & 0xf'ffff] = tag;
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_;
}
}
} }
if(tags.find(address) == tags.end()) { // Dispense with the single-byte case trivially.
printf("Access to unexpected RAM address\n"); 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. static constexpr uint32_t NoWriteBack = 0; // A low byte address of 0 can't require write-back.