1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-05 06:05:27 +00:00
CLK/InstructionSets/x86/Implementation/PerformImplementation.hpp

487 lines
19 KiB
C++
Raw Normal View History

2023-10-05 18:37:58 +00:00
//
2023-10-12 17:54:51 +00:00
//
2023-10-05 18:37:58 +00:00
// PerformImplementation.hpp
// Clock Signal
//
// Created by Thomas Harte on 05/10/2023.
// Copyright © 2023 Thomas Harte. All rights reserved.
//
#ifndef PerformImplementation_h
#define PerformImplementation_h
#include "Arithmetic.hpp"
#include "BCD.hpp"
#include "FlowControl.hpp"
#include "InOut.hpp"
#include "LoadStore.hpp"
#include "Logical.hpp"
#include "Repetition.hpp"
2023-11-06 02:42:22 +00:00
#include "Resolver.hpp"
#include "ShiftRoll.hpp"
#include "Stack.hpp"
2023-10-05 19:49:07 +00:00
#include "../Interrupts.hpp"
#include "../AccessType.hpp"
2023-10-16 19:40:24 +00:00
2023-10-06 15:10:54 +00:00
//
// Comments throughout headers above come from the 1997 edition of the
// Intel Architecture Software Developers Manual; that year all such
// definitions still fitted within a single volume, Volume 2.
2023-10-06 15:10:54 +00:00
//
// Order Number 243191; e.g. https://www.ardent-tool.com/CPU/docs/Intel/IA/243191-002.pdf
//
namespace InstructionSet::x86 {
2023-10-08 17:34:28 +00:00
2023-10-05 18:37:58 +00:00
template <
DataSize data_size,
2023-10-25 20:00:01 +00:00
AddressSize address_size,
typename InstructionT,
typename ContextT
2023-10-05 18:37:58 +00:00
> void perform(
const InstructionT &instruction,
ContextT &context
2023-10-05 18:37:58 +00:00
) {
using IntT = typename DataSizeType<data_size>::type;
2023-10-25 20:00:01 +00:00
using AddressT = typename AddressSizeType<address_size>::type;
// Establish source() and destination() shorthands to fetch data if necessary.
//
// C++17, which this project targets at the time of writing, does not provide templatised lambdas.
// So the following division is in part a necessity.
//
// (though GCC offers C++20 syntax as an extension, and Clang seems to follow along, so maybe I'm overthinking)
IntT immediate;
2023-11-07 15:09:04 +00:00
const auto source_r = [&]() -> read_t<IntT> {
return resolve<IntT, AccessType::Read>(
instruction,
instruction.source().source(),
instruction.source(),
context,
nullptr,
&immediate);
};
2023-11-07 15:09:04 +00:00
const auto source_rmw = [&]() -> modify_t<IntT> {
return resolve<IntT, AccessType::ReadModifyWrite>(
instruction,
instruction.source().source(),
instruction.source(),
context,
nullptr,
&immediate);
};
2023-11-07 15:09:04 +00:00
const auto destination_r = [&]() -> read_t<IntT> {
return resolve<IntT, AccessType::Read>(
2023-10-29 20:19:10 +00:00
instruction,
instruction.destination().source(),
instruction.destination(),
context,
2023-10-29 20:19:10 +00:00
nullptr,
&immediate);
};
2023-11-07 15:09:04 +00:00
const auto destination_w = [&]() -> write_t<IntT> {
return resolve<IntT, AccessType::Write>(
2023-10-29 20:19:10 +00:00
instruction,
instruction.destination().source(),
instruction.destination(),
context,
2023-10-29 20:19:10 +00:00
nullptr,
&immediate);
};
2023-11-07 15:09:04 +00:00
const auto destination_rmw = [&]() -> modify_t<IntT> {
return resolve<IntT, AccessType::ReadModifyWrite>(
instruction,
instruction.destination().source(),
instruction.destination(),
context,
nullptr,
&immediate);
};
2023-10-11 19:08:04 +00:00
// Performs a displacement jump only if @c condition is true.
2023-10-11 18:36:42 +00:00
const auto jcc = [&](bool condition) {
Primitive::jump(
condition,
instruction.displacement(),
context);
2023-10-11 18:36:42 +00:00
};
2023-10-13 18:44:22 +00:00
const auto shift_count = [&]() -> uint8_t {
static constexpr uint8_t mask = (ContextT::model != Model::i8086) ? 0x1f : 0xff;
switch(instruction.source().source()) {
2023-10-13 18:44:22 +00:00
case Source::None: return 1;
2023-10-13 19:34:06 +00:00
case Source::Immediate: return uint8_t(instruction.operand()) & mask;
default: return context.registers.cl() & mask;
2023-10-13 18:44:22 +00:00
}
};
2023-10-11 19:08:04 +00:00
// Some instructions use a pair of registers as an extended accumulator — DX:AX or EDX:EAX.
// The two following return the high and low parts of that pair; they also work in Byte mode to return AH:AL,
// i.e. AX split into high and low parts.
const auto pair_high = [&]() -> IntT& {
if constexpr (data_size == DataSize::Byte) return context.registers.ah();
else if constexpr (data_size == DataSize::Word) return context.registers.dx();
else if constexpr (data_size == DataSize::DWord) return context.registers.edx();
2023-10-11 18:36:42 +00:00
};
2023-10-11 19:08:04 +00:00
const auto pair_low = [&]() -> IntT& {
if constexpr (data_size == DataSize::Byte) return context.registers.al();
else if constexpr (data_size == DataSize::Word) return context.registers.ax();
else if constexpr (data_size == DataSize::DWord) return context.registers.eax();
2023-10-11 18:36:42 +00:00
};
// For the string operations, evaluate to either SI and DI or ESI and EDI, depending on the address size.
const auto eSI = [&]() -> AddressT& {
if constexpr (std::is_same_v<AddressT, uint16_t>) {
return context.registers.si();
} else {
return context.registers.esi();
}
};
const auto eDI = [&]() -> AddressT& {
if constexpr (std::is_same_v<AddressT, uint16_t>) {
return context.registers.di();
} else {
return context.registers.edi();
}
};
// For counts, provide either eCX or CX depending on address size.
const auto eCX = [&]() -> AddressT& {
if constexpr (std::is_same_v<AddressT, uint16_t>) {
return context.registers.cx();
} else {
return context.registers.ecx();
}
};
2023-10-22 02:52:50 +00:00
// Gets the port for an IN or OUT; these are always 16-bit.
const auto port = [&](Source source) -> uint16_t {
switch(source) {
2023-10-27 18:04:23 +00:00
case Source::DirectAddress: return instruction.offset();
default: return context.registers.dx();
2023-10-22 02:52:50 +00:00
}
};
// Guide to the below:
//
2023-11-08 16:23:21 +00:00
// * use hard-coded register names where appropriate, otherwise use the source_X() and destination_X() lambdas;
// * return directly if there is definitely no possible write back to RAM;
2023-11-08 16:23:21 +00:00
// * break if there's a chance of writeback.
2023-10-27 03:19:31 +00:00
switch(instruction.operation()) {
2023-10-09 15:46:59 +00:00
default:
assert(false);
2023-11-08 16:23:21 +00:00
case Operation::ESC:
case Operation::NOP: return;
case Operation::AAA: Primitive::aaa(context.registers.axp(), context); return;
case Operation::AAD: Primitive::aad(context.registers.axp(), instruction.operand(), context); return;
case Operation::AAM: Primitive::aam(context.registers.axp(), instruction.operand(), context); return;
case Operation::AAS: Primitive::aas(context.registers.axp(), context); return;
case Operation::DAA: Primitive::daa(context.registers.al(), context); return;
case Operation::DAS: Primitive::das(context.registers.al(), context); return;
2023-10-11 19:08:04 +00:00
case Operation::CBW: Primitive::cbw(pair_low()); return;
case Operation::CWD: Primitive::cwd(pair_high(), pair_low()); return;
2023-10-09 18:54:14 +00:00
case Operation::HLT: context.flow_controller.halt(); return;
case Operation::WAIT: context.flow_controller.wait(); return;
2023-10-09 20:21:04 +00:00
2023-11-08 16:23:21 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
2023-11-08 16:23:21 +00:00
case Operation::TEST:
Primitive::test<IntT>(destination_r(), source_r(), context);
return;
2023-10-11 02:15:33 +00:00
2023-11-07 14:58:42 +00:00
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;
2023-10-09 20:21:04 +00:00
2023-11-07 14:58:42 +00:00
case Operation::INC: Primitive::inc<IntT>(destination_rmw(), context); break;
case Operation::DEC: Primitive::dec<IntT>(destination_rmw(), context); break;
2023-10-10 19:57:33 +00:00
2023-11-07 14:58:42 +00:00
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.
2023-10-09 15:46:59 +00:00
2023-11-07 14:58:42 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
case Operation::RETfar: Primitive::ret_far(instruction, context); return;
case Operation::INT: interrupt(instruction.operand(), context); return;
case Operation::INTO: Primitive::into(context); return;
case Operation::SAHF: Primitive::sahf(context.registers.ah(), context); return;
case Operation::LAHF: Primitive::lahf(context.registers.ah(), context); return;
2023-11-08 16:23:21 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
2023-11-07 14:58:42 +00:00
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;
case Operation::CLI: Primitive::cli(context); return;
case Operation::STC: Primitive::stc(context); return;
case Operation::STD: Primitive::std(context); return;
case Operation::STI: Primitive::sti(context); return;
case Operation::CMC: Primitive::cmc(context); return;
2023-10-11 02:34:42 +00:00
2023-11-07 14:58:42 +00:00
case Operation::XCHG: Primitive::xchg<IntT>(destination_rmw(), source_rmw()); break;
2023-10-12 19:52:05 +00:00
2023-11-07 15:09:04 +00:00
case Operation::SALC: Primitive::salc(context.registers.al(), context); return;
2023-10-12 19:52:05 +00:00
case Operation::SETMO:
if constexpr (ContextT::model == Model::i8086) {
2023-11-07 14:58:42 +00:00
Primitive::setmo<IntT>(destination_w(), context);
2023-10-29 20:19:10 +00:00
break;
2023-10-12 19:52:05 +00:00
} else {
2023-11-08 16:23:21 +00:00
// TODO: perform ENTER as of the 80186.
static_assert(int(Operation::SETMO) == int(Operation::ENTER));
2023-10-12 19:52:05 +00:00
}
return;
case Operation::SETMOC:
if constexpr (ContextT::model == Model::i8086) {
2023-11-01 02:04:26 +00:00
// Test CL out here to avoid taking a reference to memory if
// no write is going to occur.
if(context.registers.cl()) {
2023-11-07 14:58:42 +00:00
Primitive::setmo<IntT>(destination_w(), context);
2023-11-01 02:04:26 +00:00
}
2023-10-29 20:19:10 +00:00
break;
2023-10-12 19:52:05 +00:00
} else {
2023-11-08 16:23:21 +00:00
// TODO: perform BOUND as of the 80186.
static_assert(int(Operation::SETMOC) == int(Operation::BOUND));
2023-10-12 19:52:05 +00:00
}
return;
2023-10-13 01:12:03 +00:00
case Operation::OUT: Primitive::out<IntT>(port(instruction.destination().source()), pair_low(), context); return;
2023-11-08 16:23:21 +00:00
case Operation::IN: Primitive::in<IntT>(port(instruction.source().source()), pair_low(), context); return;
2023-10-22 02:37:25 +00:00
case Operation::XLAT: Primitive::xlat<AddressT>(instruction, context); return;
2023-10-18 19:59:39 +00:00
case Operation::POP: destination_w() = Primitive::pop<IntT, false>(context); break;
2023-11-07 14:58:42 +00:00
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); return;
case Operation::PUSHF: Primitive::pushf(context); return;
case Operation::CMPS:
Primitive::cmps<IntT, AddressT, Repetition::None>(instruction, eCX(), eSI(), eDI(), context);
return;
case Operation::CMPS_REPE:
Primitive::cmps<IntT, AddressT, Repetition::RepE>(instruction, eCX(), eSI(), eDI(), context);
return;
case Operation::CMPS_REPNE:
Primitive::cmps<IntT, AddressT, Repetition::RepNE>(instruction, eCX(), eSI(), eDI(), context);
return;
case Operation::SCAS:
Primitive::scas<IntT, AddressT, Repetition::None>(eCX(), eDI(), pair_low(), context);
return;
case Operation::SCAS_REPE:
Primitive::scas<IntT, AddressT, Repetition::RepE>(eCX(), eDI(), pair_low(), context);
return;
case Operation::SCAS_REPNE:
Primitive::scas<IntT, AddressT, Repetition::RepNE>(eCX(), eDI(), pair_low(), context);
return;
case Operation::LODS:
Primitive::lods<IntT, AddressT, Repetition::None>(instruction, eCX(), eSI(), pair_low(), context);
return;
case Operation::LODS_REP:
Primitive::lods<IntT, AddressT, Repetition::Rep>(instruction, eCX(), eSI(), pair_low(), context);
return;
case Operation::MOVS:
Primitive::movs<IntT, AddressT, Repetition::None>(instruction, eCX(), eSI(), eDI(), context);
break;
case Operation::MOVS_REP:
Primitive::movs<IntT, AddressT, Repetition::Rep>(instruction, eCX(), eSI(), eDI(), context);
break;
case Operation::STOS:
Primitive::stos<IntT, AddressT, Repetition::None>(eCX(), eDI(), pair_low(), context);
break;
case Operation::STOS_REP:
Primitive::stos<IntT, AddressT, Repetition::Rep>(eCX(), eDI(), pair_low(), context);
2023-10-21 01:54:30 +00:00
break;
2023-10-22 02:52:50 +00:00
case Operation::OUTS:
Primitive::outs<IntT, AddressT, Repetition::None>(instruction, eCX(), context.registers.dx(), eSI(), context);
return;
case Operation::OUTS_REP:
Primitive::outs<IntT, AddressT, Repetition::Rep>(instruction, eCX(), context.registers.dx(), eSI(), context);
return;
2023-10-22 02:52:50 +00:00
case Operation::INS:
Primitive::ins<IntT, AddressT, Repetition::None>(eCX(), context.registers.dx(), eDI(), context);
break;
case Operation::INS_REP:
Primitive::ins<IntT, AddressT, Repetition::Rep>(eCX(), context.registers.dx(), eDI(), context);
2023-10-22 02:52:50 +00:00
break;
2023-10-05 18:37:58 +00:00
}
// Write to memory if required to complete this operation.
//
2023-11-08 16:23:21 +00:00
// This is not currently handled via RAII because of the amount of context that would need to place onto the stack;
// instead code has been set up to make sure there is only at most one writeable target on loan for potential
// write back. I might flip-flop on this, especially if I can verify whether extra stack context is easily
// optimised out.
context.memory.template write_back<IntT>();
2023-10-05 18:37:58 +00:00
}
2023-11-08 16:23:21 +00:00
//
// Public function; just a trampoline into a version of perform templated on data and address size.
//
// Which, yes, means there's an outer switch leading to an inner switch, which could be reduced to one big switch.
// It'd be a substantial effort to find the most neat expression of that, I think, so it is not currently done.
//
template <
2023-10-05 18:37:58 +00:00
typename InstructionT,
typename ContextT
2023-10-05 18:37:58 +00:00
> void perform(
const InstructionT &instruction,
ContextT &context
2023-10-05 18:37:58 +00:00
) {
2023-10-25 20:00:01 +00:00
auto size = [](DataSize operation_size, AddressSize address_size) constexpr -> int {
return int(operation_size) + (int(address_size) << 2);
};
2023-10-25 20:15:08 +00:00
// Dispatch to a function specialised on data and address size.
2023-10-25 20:00:01 +00:00
switch(size(instruction.operation_size(), instruction.address_size())) {
// 16-bit combinations.
case size(DataSize::Byte, AddressSize::b16):
perform<DataSize::Byte, AddressSize::b16>(instruction, context);
2023-10-25 20:00:01 +00:00
return;
case size(DataSize::Word, AddressSize::b16):
perform<DataSize::Word, AddressSize::b16>(instruction, context);
2023-10-25 20:00:01 +00:00
return;
// 32-bit combinations.
2023-10-25 20:15:08 +00:00
//
// The if constexprs below ensure that `perform` isn't compiled for incompatible data or address size and
// model combinations. So if a caller nominates a 16-bit model it can supply registers and memory objects
// that don't implement 32-bit registers or accesses.
2023-10-25 20:00:01 +00:00
case size(DataSize::Byte, AddressSize::b32):
if constexpr (is_32bit(ContextT::model)) {
perform<DataSize::Byte, AddressSize::b32>(instruction, context);
2023-10-25 20:00:01 +00:00
return;
}
break;
2023-10-25 20:00:01 +00:00
case size(DataSize::Word, AddressSize::b32):
if constexpr (is_32bit(ContextT::model)) {
perform<DataSize::Word, AddressSize::b32>(instruction, context);
2023-10-25 20:00:01 +00:00
return;
}
break;
2023-10-25 20:00:01 +00:00
case size(DataSize::DWord, AddressSize::b16):
if constexpr (is_32bit(ContextT::model)) {
perform<DataSize::DWord, AddressSize::b16>(instruction, context);
2023-10-25 20:00:01 +00:00
return;
2023-10-10 01:50:17 +00:00
}
break;
2023-10-25 20:00:01 +00:00
case size(DataSize::DWord, AddressSize::b32):
if constexpr (is_32bit(ContextT::model)) {
perform<DataSize::DWord, AddressSize::b32>(instruction, context);
2023-10-25 20:00:01 +00:00
return;
}
break;
default: break;
2023-10-05 18:37:58 +00:00
}
2023-10-25 20:00:01 +00:00
2023-10-25 20:15:08 +00:00
// This is reachable only if the data and address size combination in use isn't available
// on the processor model nominated.
2023-10-25 20:00:01 +00:00
assert(false);
}
2023-10-05 18:37:58 +00:00
template <
typename ContextT
> void interrupt(
int index,
ContextT &context
) {
const uint32_t address = static_cast<uint32_t>(index) << 2;
context.memory.preauthorise_read(address, sizeof(uint16_t) * 2);
context.memory.preauthorise_stack_write(sizeof(uint16_t) * 3);
2023-11-02 19:37:59 +00:00
const uint16_t ip = context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(address);
const uint16_t cs = context.memory.template access<uint16_t, AccessType::PreauthorisedRead>(address + 2);
auto flags = context.flags.get();
Primitive::push<uint16_t, true>(flags, context);
context.flags.template set_from<Flag::Interrupt, Flag::Trap>(0);
// Push CS and IP.
Primitive::push<uint16_t, true>(context.registers.cs(), context);
Primitive::push<uint16_t, true>(context.registers.ip(), context);
// Set new destination.
context.flow_controller.jump(cs, ip);
}
2023-10-05 18:37:58 +00:00
}
#endif /* PerformImplementation_h */