diff --git a/InstructionSets/x86/Implementation/Arithmetic.hpp b/InstructionSets/x86/Implementation/Arithmetic.hpp new file mode 100644 index 000000000..971a877cb --- /dev/null +++ b/InstructionSets/x86/Implementation/Arithmetic.hpp @@ -0,0 +1,358 @@ +// +// Arithmetic.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Arithmetic_hpp +#define Arithmetic_hpp + +#include "../AccessType.hpp" +#include "../Interrupts.hpp" +#include "../Perform.hpp" + +#include "../../../Numeric/Carry.hpp" + +namespace InstructionSet::x86::Primitive { + +template +void add( + modify_t destination, + read_t source, + ContextT &context +) { + /* + DEST ← DEST + SRC [+ CF]; + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination + source + (with_carry ? context.flags.template carry_bit() : 0); + + context.flags.template set_from( + Numeric::carried_out() - 1>(destination, source, result)); + context.flags.template set_from( + Numeric::carried_in<4>(destination, source, result)); + context.flags.template set_from( + Numeric::overflow(destination, source, result)); + + context.flags.template set_from(result); + + destination = result; +} + +template +void sub( + access_t destination, + read_t source, + ContextT &context +) { + /* + DEST ← DEST - (SRC [+ CF]); + */ + /* + The OF, SF, ZF, AF, CF, and PF flags are set according to the result. + */ + const IntT result = destination - source - (with_borrow ? context.flags.template carry_bit() : 0); + + context.flags.template set_from( + Numeric::carried_out() - 1>(destination, source, result)); + context.flags.template set_from( + Numeric::carried_in<4>(destination, source, result)); + context.flags.template set_from( + Numeric::overflow(destination, source, result)); + + context.flags.template set_from(result); + + if constexpr (is_writeable(destination_type)) { + destination = result; + } +} + +template +void test( + read_t destination, + read_t source, + ContextT &context +) { + /* + TEMP ← SRC1 AND SRC2; + SF ← MSB(TEMP); + IF TEMP = 0 + THEN ZF ← 0; + ELSE ZF ← 1; + FI: + PF ← BitwiseXNOR(TEMP[0:7]); + CF ← 0; + OF ← 0; + */ + /* + The OF and CF flags are cleared to 0. + The SF, ZF, and PF flags are set according to the result (see the “Operation” section above). + The state of the AF flag is undefined. + */ + const IntT result = destination & source; + + context.flags.template set_from(0); + context.flags.template set_from(result); +} + +template +void mul( + modify_t destination_high, + modify_t destination_low, + read_t source, + ContextT &context +) { + /* + IF byte operation + THEN + AX ← AL * SRC + ELSE (* word or doubleword operation *) + IF OperandSize = 16 THEN + DX:AX ← AX * SRC + ELSE (* OperandSize = 32 *) + EDX:EAX ← EAX * SRC + FI; + */ + /* + The OF and CF flags are cleared to 0 if the upper half of the result is 0; + otherwise, they are set to 1. The SF, ZF, AF, and PF flags are undefined. + */ + destination_high = (destination_low * source) >> (8 * sizeof(IntT)); + destination_low *= source; + context.flags.template set_from(destination_high); +} + +template +void imul( + modify_t destination_high, + modify_t destination_low, + read_t source, + ContextT &context +) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + IF (OperandSize = 8) + THEN + AX ← AL ∗ SRC (* signed multiplication *) + IF (AX = SignExtend(AL)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + ELSE IF OperandSize = 16 + THEN + DX:AX ← AX ∗ SRC (* signed multiplication *) + IF (DX:AX = SignExtend(AX)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + ELSE (* OperandSize = 32 *) + EDX:EAX ← EAX ∗ SRC (* signed multiplication *) + IF (EDX:EAX = SignExtend(EAX)) + THEN CF = 0; OF = 0; + ELSE CF = 1; OF = 1; + FI; + FI; + */ + using sIntT = typename std::make_signed::type; + destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)); + destination_low = IntT(sIntT(destination_low) * sIntT(source)); + + const auto sign_extension = (destination_low & Numeric::top_bit()) ? IntT(~0) : 0; + context.flags.template set_from(destination_high != sign_extension); +} + +template +void div( + modify_t destination_high, + modify_t destination_low, + read_t source, + ContextT &context +) { + /* + IF SRC = 0 + THEN #DE; (* divide error *) + FI; + IF OperandSize = 8 (* word/byte operation *) + THEN + temp ← AX / SRC; + IF temp > FFH + THEN #DE; (* divide error *) ; + ELSE + AL ← temp; + AH ← AX MOD SRC; + FI; + ELSE + IF OperandSize = 16 (* doubleword/word operation *) + THEN + temp ← DX:AX / SRC; + IF temp > FFFFH + THEN #DE; (* divide error *) ; + ELSE + AX ← temp; + DX ← DX:AX MOD SRC; + FI; + ELSE (* quadword/doubleword operation *) + temp ← EDX:EAX / SRC; + IF temp > FFFFFFFFH + THEN #DE; (* divide error *) ; + ELSE + EAX ← temp; + EDX ← EDX:EAX MOD SRC; + FI; + FI; + FI; + */ + /* + The CF, OF, SF, ZF, AF, and PF flags are undefined. + */ + if(!source) { + interrupt(Interrupt::DivideError, context); + return; + } + + // TEMPORARY HACK. Will not work with DWords. + const uint32_t dividend = (destination_high << (8 * sizeof(IntT))) + destination_low; + const auto result = dividend / source; + if(IntT(result) != result) { + interrupt(Interrupt::DivideError, context); + return; + } + + destination_low = IntT(result); + destination_high = dividend % source; +} + +template +void idiv( + modify_t destination_high, + modify_t destination_low, + read_t source, + ContextT &context +) { + /* + IF SRC = 0 + THEN #DE; (* divide error *) + FI; + IF OperandSize = 8 (* word/byte operation *) + THEN + temp ← AX / SRC; (* signed division *) + IF (temp > 7FH) OR (temp < 80H) (* if a positive result is greater than 7FH or a negative result is less than 80H *) + THEN #DE; (* divide error *) ; + ELSE + AL ← temp; + AH ← AX MOD SRC; + FI; + ELSE + IF OperandSize = 16 (* doubleword/word operation *) + THEN + temp ← DX:AX / SRC; (* signed division *) + IF (temp > 7FFFH) OR (temp < 8000H) (* if a positive result is greater than 7FFFH or a negative result is less than 8000H *) + THEN #DE; (* divide error *) ; + ELSE + AX ← temp; + DX ← DX:AX MOD SRC; + FI; + ELSE (* quadword/doubleword operation *) + temp ← EDX:EAX / SRC; (* signed division *) + IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH or a negative result is less than 80000000H *) + THEN #DE; (* divide error *) ; + ELSE + EAX ← temp; + EDX ← EDX:EAX MOD SRC; + FI; + FI; + FI; + */ + /* + The CF, OF, SF, ZF, AF, and PF flags are undefined. + */ + if(!source) { + interrupt(Interrupt::DivideError, context); + return; + } + + // TEMPORARY HACK. Will not work with DWords. + using sIntT = typename std::make_signed::type; + const int32_t dividend = (sIntT(destination_high) << (8 * sizeof(IntT))) + destination_low; + const auto result = dividend / sIntT(source); + if(sIntT(result) != result) { + interrupt(Interrupt::DivideError, context); + return; + } + + destination_low = IntT(result); + destination_high = dividend % sIntT(source); +} + +template +void inc( + modify_t destination, + ContextT &context +) { + /* + DEST ← DEST + 1; + */ + /* + The CF flag is not affected. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + ++destination; + + context.flags.template set_from(destination == Numeric::top_bit()); + context.flags.template set_from(((destination - 1) ^ destination) & 0x10); + context.flags.template set_from(destination); +} + +template +void dec( + modify_t destination, + ContextT &context +) { + /* + DEST ← DEST - 1; + */ + /* + The CF flag is not affected. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + context.flags.template set_from(destination == Numeric::top_bit()); + + --destination; + + context.flags.template set_from(destination); + context.flags.template set_from(((destination + 1) ^ destination) & 0x10); +} + +template +void neg( + modify_t destination, + ContextT &context +) { + /* + IF DEST = 0 + THEN CF ← 0 + ELSE CF ← 1; + FI; + DEST ← –(DEST) + */ + /* + The CF flag cleared to 0 if the source operand is 0; otherwise it is set to 1. + The OF, SF, ZF, AF, and PF flags are set according to the result. + */ + context.flags.template set_from(Numeric::carried_in<4>(IntT(0), destination, IntT(-destination))); + + destination = -destination; + + context.flags.template set_from(destination); + context.flags.template set_from(destination == Numeric::top_bit()); + context.flags.template set_from(destination); +} + +} + +#endif /* Arithmetic_hpp */ diff --git a/InstructionSets/x86/Implementation/BCD.hpp b/InstructionSets/x86/Implementation/BCD.hpp new file mode 100644 index 000000000..f8de9e6fd --- /dev/null +++ b/InstructionSets/x86/Implementation/BCD.hpp @@ -0,0 +1,243 @@ +// +// BCD.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef BCD_h +#define BCD_h + +#include "../AccessType.hpp" + +#include "../../../Numeric/RegisterSizes.hpp" + +namespace InstructionSet::x86::Primitive { + +template +void aaa( + CPU::RegisterPair16 &ax, + ContextT &context +) { // P. 313 + /* + IF ((AL AND 0FH) > 9) OR (AF = 1) + THEN + AL ← (AL + 6); + AH ← AH + 1; + AF ← 1; + CF ← 1; + ELSE + AF ← 0; + CF ← 0; + FI; + AL ← AL AND 0FH; + */ + /* + The AF and CF flags are set to 1 if the adjustment results in a decimal carry; + otherwise they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. + */ + if((ax.halves.low & 0x0f) > 9 || context.flags.template flag()) { + ax.halves.low += 6; + ++ax.halves.high; + context.flags.template set_from(1); + } else { + context.flags.template set_from(0); + } + ax.halves.low &= 0x0f; +} + +template +void aad( + CPU::RegisterPair16 &ax, + uint8_t imm, + ContextT &context +) { + /* + tempAL ← AL; + tempAH ← AH; + AL ← (tempAL + (tempAH * imm8)) AND FFH; (* imm8 is set to 0AH for the AAD mnemonic *) + AH ← 0 + */ + /* + The SF, ZF, and PF flags are set according to the result; + the OF, AF, and CF flags are undefined. + */ + ax.halves.low = ax.halves.low + (ax.halves.high * imm); + ax.halves.high = 0; + context.flags.template set_from(ax.halves.low); +} + +template +void aam( + CPU::RegisterPair16 &ax, + uint8_t imm, + ContextT &context +) { + /* + tempAL ← AL; + AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) + AL ← tempAL MOD imm8; + */ + /* + The SF, ZF, and PF flags are set according to the result. + The OF, AF, and CF flags are undefined. + */ + /* + If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. + */ + if(!imm) { + interrupt(Interrupt::DivideError, context); + return; + } + + ax.halves.high = ax.halves.low / imm; + ax.halves.low = ax.halves.low % imm; + context.flags.template set_from(ax.halves.low); +} + +template +void aas( + CPU::RegisterPair16 &ax, + ContextT &context +) { + /* + IF ((AL AND 0FH) > 9) OR (AF = 1) + THEN + AL ← AL – 6; + AH ← AH – 1; + AF ← 1; + CF ← 1; + ELSE + CF ← 0; + AF ← 0; + FI; + AL ← AL AND 0FH; + */ + /* + The AF and CF flags are set to 1 if there is a decimal borrow; + otherwise, they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. + */ + if((ax.halves.low & 0x0f) > 9 || context.flags.template flag()) { + ax.halves.low -= 6; + --ax.halves.high; + context.flags.template set_from(1); + } else { + context.flags.template set_from(0); + } + ax.halves.low &= 0x0f; +} + +template +void daa( + uint8_t &al, + ContextT &context +) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + old_AL ← AL; + old_CF ← CF; + CF ← 0; + + IF (((AL AND 0FH) > 9) or AF = 1) + THEN + AL ← AL + 6; + CF ← old_CF OR CarryFromLastAddition; (* CF OR carry from AL ← AL + 6 *) + AF ← 1; + ELSE + AF ← 0; + FI; + IF ((old_AL > 99H) or old_CF = 1) + THEN + AL ← AL + 60H; + CF ← 1; + ELSE + CF ← 0; + FI; + */ + /* + The CF and AF flags are set if the adjustment of the value results in a + decimal carry in either digit of the result (see the “Operation” section above). + The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. + */ + const uint8_t old_al = al; + const auto old_carry = context.flags.template flag(); + context.flags.template set_from(0); + + if((al & 0x0f) > 0x09 || context.flags.template flag()) { + context.flags.template set_from(old_carry | (al > 0xf9)); + al += 0x06; + context.flags.template set_from(1); + } else { + context.flags.template set_from(0); + } + + if(old_al > 0x99 || old_carry) { + al += 0x60; + context.flags.template set_from(1); + } else { + context.flags.template set_from(0); + } + + context.flags.template set_from(al); +} + +template +void das( + uint8_t &al, + ContextT &context +) { + /* + (as modified by https://www.felixcloutier.com/x86/daa ...) + + old_AL ← AL; + old_CF ← CF; + CF ← 0; + + IF (((AL AND 0FH) > 9) or AF = 1) + THEN + AL ← AL - 6; + CF ← old_CF OR CarryFromLastAddition; (* CF OR borrow from AL ← AL - 6 *) + AF ← 1; + ELSE + AF ← 0; + FI; + IF ((old_AL > 99H) or old_CF = 1) + THEN + AL ← AL - 60H; + CF ← 1; + ELSE + CF ← 0; + FI; + */ + /* + The CF and AF flags are set if the adjustment of the value results in a + decimal carry in either digit of the result (see the “Operation” section above). + The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. + */ + const uint8_t old_al = al; + const auto old_carry = context.flags.template flag(); + context.flags.template set_from(0); + + if((al & 0x0f) > 0x09 || context.flags.template flag()) { + context.flags.template set_from(old_carry | (al < 0x06)); + al -= 0x06; + context.flags.template set_from(1); + } else { + context.flags.template set_from(0); + } + + if(old_al > 0x99 || old_carry) { + al -= 0x60; + context.flags.template set_from(1); + } else { + context.flags.template set_from(0); + } + + context.flags.template set_from(al); +} + +} + +#endif /* BCD_h */ diff --git a/InstructionSets/x86/Implementation/FlowControl.hpp b/InstructionSets/x86/Implementation/FlowControl.hpp new file mode 100644 index 000000000..a92a094a2 --- /dev/null +++ b/InstructionSets/x86/Implementation/FlowControl.hpp @@ -0,0 +1,222 @@ +// +// FlowControl.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef FlowControl_hpp +#define FlowControl_hpp + +#include "Resolver.hpp" +#include "Stack.hpp" +#include "../AccessType.hpp" + +namespace InstructionSet::x86::Primitive { + +template +void jump( + bool condition, + IntT displacement, + ContextT &context +) { + /* + IF condition + THEN + EIP ← EIP + SignExtend(DEST); + IF OperandSize = 16 + THEN + EIP ← EIP AND 0000FFFFH; + FI; + FI; + */ + + // TODO: proper behaviour in 32-bit. + if(condition) { + context.flow_controller.jump(context.registers.ip() + displacement); + } +} + +template +void loop( + modify_t counter, + OffsetT displacement, + ContextT &context +) { + --counter; + if(counter) { + context.flow_controller.jump(context.registers.ip() + displacement); + } +} + +template +void loope( + modify_t counter, + OffsetT displacement, + ContextT &context +) { + --counter; + if(counter && context.flags.template flag()) { + context.flow_controller.jump(context.registers.ip() + displacement); + } +} + +template +void loopne( + modify_t counter, + OffsetT displacement, + ContextT &context +) { + --counter; + if(counter && !context.flags.template flag()) { + context.flow_controller.jump(context.registers.ip() + displacement); + } +} + +template +void call_relative( + IntT offset, + ContextT &context +) { + push(context.registers.ip(), context); + context.flow_controller.jump(context.registers.ip() + offset); +} + +template +void call_absolute( + read_t target, + ContextT &context +) { + push(context.registers.ip(), context); + context.flow_controller.jump(target); +} + +template +void jump_absolute( + read_t target, + ContextT &context +) { + context.flow_controller.jump(target); +} + +template +void call_far( + InstructionT &instruction, + ContextT &context +) { + // TODO: eliminate 16-bit assumption below. + const Source source_segment = instruction.data_segment(); + context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2); + + uint16_t source_address; + const auto pointer = instruction.destination(); + switch(pointer.source()) { + default: + case Source::Immediate: + push(context.registers.cs(), context); + push(context.registers.ip(), context); + context.flow_controller.jump(instruction.segment(), instruction.offset()); + return; + + case Source::Indirect: + source_address = address(instruction, pointer, context); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, context); + break; + case Source::DirectAddress: + source_address = address(instruction, pointer, context); + break; + } + + context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); + const uint16_t offset = context.memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = context.memory.template access(source_segment, source_address); + + // At least on an 8086, the stack writes occur after the target address read. + push(context.registers.cs(), context); + push(context.registers.ip(), context); + + context.flow_controller.jump(segment, offset); +} + +template +void jump_far( + InstructionT &instruction, + ContextT &context +) { + // TODO: eliminate 16-bit assumption below. + uint16_t source_address = 0; + const auto pointer = instruction.destination(); + switch(pointer.source()) { + default: + case Source::Immediate: context.flow_controller.jump(instruction.segment(), instruction.offset()); return; + + case Source::Indirect: + source_address = address(instruction, pointer, context); + break; + case Source::IndirectNoBase: + source_address = address(instruction, pointer, context); + break; + case Source::DirectAddress: + source_address = address(instruction, pointer, context); + break; + } + + const Source source_segment = instruction.data_segment(); + context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); + + const uint16_t offset = context.memory.template access(source_segment, source_address); + source_address += 2; + const uint16_t segment = context.memory.template access(source_segment, source_address); + context.flow_controller.jump(segment, offset); +} + +template +void iret( + ContextT &context +) { + // TODO: all modes other than 16-bit real mode. + context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3); + const auto ip = pop(context); + const auto cs = pop(context); + context.flags.set(pop(context)); + context.flow_controller.jump(cs, ip); +} + +template +void ret_near( + InstructionT instruction, + ContextT &context +) { + const auto ip = pop(context); + context.registers.sp() += instruction.operand(); + context.flow_controller.jump(ip); +} + +template +void ret_far( + InstructionT instruction, + ContextT &context +) { + context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2); + const auto ip = pop(context); + const auto cs = pop(context); + context.registers.sp() += instruction.operand(); + context.flow_controller.jump(cs, ip); +} + +template +void into( + ContextT &context +) { + if(context.flags.template flag()) { + interrupt(Interrupt::OnOverflow, context); + } +} + +} + +#endif /* FlowControl_hpp */ diff --git a/InstructionSets/x86/Implementation/InOut.hpp b/InstructionSets/x86/Implementation/InOut.hpp new file mode 100644 index 000000000..21ed3aaa0 --- /dev/null +++ b/InstructionSets/x86/Implementation/InOut.hpp @@ -0,0 +1,36 @@ +// +// InOut.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef InOut_h +#define InOut_h + +#include "../AccessType.hpp" + +namespace InstructionSet::x86::Primitive { + +template +void out( + uint16_t port, + read_t value, + ContextT &context +) { + context.io.template out(port, value); +} + +template +void in( + uint16_t port, + write_t value, + ContextT &context +) { + value = context.io.template in(port); +} + +} + +#endif /* InOut_h */ diff --git a/InstructionSets/x86/Implementation/LoadStore.hpp b/InstructionSets/x86/Implementation/LoadStore.hpp new file mode 100644 index 000000000..fc05fbb81 --- /dev/null +++ b/InstructionSets/x86/Implementation/LoadStore.hpp @@ -0,0 +1,83 @@ +// +// LoadStore.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef LoadStore_h +#define LoadStore_h + +#include "../AccessType.hpp" + +#include + +namespace InstructionSet::x86::Primitive { + +template +void xchg( + modify_t destination, + modify_t source +) { + /* + TEMP ← DEST + DEST ← SRC + SRC ← TEMP + */ + std::swap(destination, source); +} + +template +void ld( + const InstructionT &instruction, + write_t destination, + ContextT &context +) { + const auto pointer = instruction.source(); + auto source_address = address(instruction, pointer, context); + const Source source_segment = instruction.data_segment(); + + context.memory.preauthorise_read(source_segment, source_address, 4); + destination = context.memory.template access(source_segment, source_address); + source_address += 2; + switch(selector) { + case Source::DS: context.registers.ds() = context.memory.template access(source_segment, source_address); break; + case Source::ES: context.registers.es() = context.memory.template access(source_segment, source_address); break; + } +} + +template +void lea( + const InstructionT &instruction, + write_t destination, + ContextT &context +) { + // TODO: address size. + destination = IntT(address(instruction, instruction.source(), context)); +} + +template +void xlat( + const InstructionT &instruction, + ContextT &context +) { + AddressT address; + if constexpr (std::is_same_v) { + address = context.registers.bx() + context.registers.al(); + } + + context.registers.al() = context.memory.template access(instruction.data_segment(), address); +} + +template +void mov( + write_t destination, + read_t source +) { + destination = source; +} + +} + +#endif /* LoadStore_h */ diff --git a/InstructionSets/x86/Implementation/Logical.hpp b/InstructionSets/x86/Implementation/Logical.hpp new file mode 100644 index 000000000..74c7816ba --- /dev/null +++ b/InstructionSets/x86/Implementation/Logical.hpp @@ -0,0 +1,146 @@ +// +// Logical.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Logical_h +#define Logical_h + +#include "../AccessType.hpp" + +namespace InstructionSet::x86::Primitive { + +template +void and_( + modify_t destination, + read_t source, + ContextT &context +) { + /* + DEST ← DEST AND SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination &= source; + + context.flags.template set_from(0); + context.flags.template set_from(destination); +} + +template +void or_( + modify_t destination, + read_t source, + ContextT &context +) { + /* + DEST ← DEST OR SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination |= source; + + context.flags.template set_from(0); + context.flags.template set_from(destination); +} + +template +void xor_( + modify_t destination, + read_t source, + ContextT &context +) { + /* + DEST ← DEST XOR SRC; + */ + /* + The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. + The state of the AF flag is undefined. + */ + destination ^= source; + + context.flags.template set_from(0); + context.flags.template set_from(destination); +} + +template +void not_( + modify_t destination +) { + /* + DEST ← NOT DEST; + */ + /* + Flags affected: none. + */ + destination = ~destination; +} + +template +void cbw( + IntT &ax +) { + constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); + constexpr IntT low_half = (1 << (sizeof(IntT) * 4)) - 1; + + if(ax & test_bit) { + ax |= ~low_half; + } else { + ax &= low_half; + } +} + +template +void cwd( + IntT &dx, + IntT ax +) { + dx = ax & Numeric::top_bit() ? IntT(~0) : IntT(0); +} + +// TODO: changes to the interrupt flag do quite a lot more in protected mode. +template +void clc(ContextT &context) { context.flags.template set_from(0); } +template +void cld(ContextT &context) { context.flags.template set_from(0); } +template +void cli(ContextT &context) { context.flags.template set_from(0); } +template +void stc(ContextT &context) { context.flags.template set_from(1); } +template +void std(ContextT &context) { context.flags.template set_from(1); } +template +void sti(ContextT &context) { context.flags.template set_from(1); } +template +void cmc(ContextT &context) { + context.flags.template set_from(!context.flags.template flag()); +} + +template +void salc( + uint8_t &al, + ContextT &context +) { + al = context.flags.template flag() ? 0xff : 0x00; +} + +template +void setmo( + write_t destination, + ContextT &context +) { + const auto result = destination = ~0; + context.flags.template set_from(0); + context.flags.template set_from(result); +} + +} + +#endif /* Logical_h */ diff --git a/InstructionSets/x86/Implementation/PerformImplementation.hpp b/InstructionSets/x86/Implementation/PerformImplementation.hpp index e9d2900da..1090d4a17 100644 --- a/InstructionSets/x86/Implementation/PerformImplementation.hpp +++ b/InstructionSets/x86/Implementation/PerformImplementation.hpp @@ -10,1512 +10,29 @@ #ifndef PerformImplementation_h #define PerformImplementation_h -#include "../../../Numeric/Carry.hpp" -#include "../../../Numeric/RegisterSizes.hpp" +#include "Arithmetic.hpp" +#include "BCD.hpp" +#include "FlowControl.hpp" +#include "InOut.hpp" +#include "LoadStore.hpp" +#include "Logical.hpp" +#include "Repetition.hpp" +#include "Resolver.hpp" +#include "ShiftRoll.hpp" +#include "Stack.hpp" + #include "../Interrupts.hpp" #include "../AccessType.hpp" -#include "Resolver.hpp" - -#include - -namespace InstructionSet::x86 { - -namespace Primitive { - -// The below takes a reference in order properly to handle PUSH SP, -// which should place the value of SP after the push onto the stack. -template -void push(IntT &value, ContextT &context) { - context.registers.sp_ -= sizeof(IntT); - if constexpr (preauthorised) { - context.memory.template preauthorised_write(Source::SS, context.registers.sp_, value); - } else { - context.memory.template access( - Source::SS, - context.registers.sp_) = value; - } - context.memory.template write_back(); -} - -template -IntT pop(ContextT &context) { - const auto value = context.memory.template access( - Source::SS, - context.registers.sp_); - context.registers.sp_ += sizeof(IntT); - return value; -} // -// Comments below on intended functioning of each operation come from the 1997 edition of the -// Intel Architecture Software Developer’s Manual; that year all such definitions still fitted within a -// single volume, Volume 2. +// Comments throughout headers above come from the 1997 edition of the +// Intel Architecture Software Developer’s Manual; that year all such +// definitions still fitted within a single volume, Volume 2. // // Order Number 243191; e.g. https://www.ardent-tool.com/CPU/docs/Intel/IA/243191-002.pdf // -template -void aaa(CPU::RegisterPair16 &ax, ContextT &context) { // P. 313 - /* - IF ((AL AND 0FH) > 9) OR (AF = 1) - THEN - AL ← (AL + 6); - AH ← AH + 1; - AF ← 1; - CF ← 1; - ELSE - AF ← 0; - CF ← 0; - FI; - AL ← AL AND 0FH; - */ - /* - The AF and CF flags are set to 1 if the adjustment results in a decimal carry; - otherwise they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. - */ - if((ax.halves.low & 0x0f) > 9 || context.flags.template flag()) { - ax.halves.low += 6; - ++ax.halves.high; - context.flags.template set_from(1); - } else { - context.flags.template set_from(0); - } - ax.halves.low &= 0x0f; -} - -template -void aad(CPU::RegisterPair16 &ax, uint8_t imm, ContextT &context) { - /* - tempAL ← AL; - tempAH ← AH; - AL ← (tempAL + (tempAH * imm8)) AND FFH; (* imm8 is set to 0AH for the AAD mnemonic *) - AH ← 0 - */ - /* - The SF, ZF, and PF flags are set according to the result; - the OF, AF, and CF flags are undefined. - */ - ax.halves.low = ax.halves.low + (ax.halves.high * imm); - ax.halves.high = 0; - context.flags.template set_from(ax.halves.low); -} - -template -void aam(CPU::RegisterPair16 &ax, uint8_t imm, ContextT &context) { - /* - tempAL ← AL; - AH ← tempAL / imm8; (* imm8 is set to 0AH for the AAD mnemonic *) - AL ← tempAL MOD imm8; - */ - /* - The SF, ZF, and PF flags are set according to the result. - The OF, AF, and CF flags are undefined. - */ - /* - If ... an immediate value of 0 is used, it will cause a #DE (divide error) exception. - */ - if(!imm) { - interrupt(Interrupt::DivideError, context); - return; - } - - ax.halves.high = ax.halves.low / imm; - ax.halves.low = ax.halves.low % imm; - context.flags.template set_from(ax.halves.low); -} - -template -void aas(CPU::RegisterPair16 &ax, ContextT &context) { - /* - IF ((AL AND 0FH) > 9) OR (AF = 1) - THEN - AL ← AL – 6; - AH ← AH – 1; - AF ← 1; - CF ← 1; - ELSE - CF ← 0; - AF ← 0; - FI; - AL ← AL AND 0FH; - */ - /* - The AF and CF flags are set to 1 if there is a decimal borrow; - otherwise, they are cleared to 0. The OF, SF, ZF, and PF flags are undefined. - */ - if((ax.halves.low & 0x0f) > 9 || context.flags.template flag()) { - ax.halves.low -= 6; - --ax.halves.high; - context.flags.template set_from(1); - } else { - context.flags.template set_from(0); - } - ax.halves.low &= 0x0f; -} - -template -void daa(uint8_t &al, ContextT &context) { - /* - (as modified by https://www.felixcloutier.com/x86/daa ...) - - old_AL ← AL; - old_CF ← CF; - CF ← 0; - - IF (((AL AND 0FH) > 9) or AF = 1) - THEN - AL ← AL + 6; - CF ← old_CF OR CarryFromLastAddition; (* CF OR carry from AL ← AL + 6 *) - AF ← 1; - ELSE - AF ← 0; - FI; - IF ((old_AL > 99H) or old_CF = 1) - THEN - AL ← AL + 60H; - CF ← 1; - ELSE - CF ← 0; - FI; - */ - /* - The CF and AF flags are set if the adjustment of the value results in a - decimal carry in either digit of the result (see the “Operation” section above). - The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. - */ - const uint8_t old_al = al; - const auto old_carry = context.flags.template flag(); - context.flags.template set_from(0); - - if((al & 0x0f) > 0x09 || context.flags.template flag()) { - context.flags.template set_from(old_carry | (al > 0xf9)); - al += 0x06; - context.flags.template set_from(1); - } else { - context.flags.template set_from(0); - } - - if(old_al > 0x99 || old_carry) { - al += 0x60; - context.flags.template set_from(1); - } else { - context.flags.template set_from(0); - } - - context.flags.template set_from(al); -} - -template -void das(uint8_t &al, ContextT &context) { - /* - (as modified by https://www.felixcloutier.com/x86/daa ...) - - old_AL ← AL; - old_CF ← CF; - CF ← 0; - - IF (((AL AND 0FH) > 9) or AF = 1) - THEN - AL ← AL - 6; - CF ← old_CF OR CarryFromLastAddition; (* CF OR borrow from AL ← AL - 6 *) - AF ← 1; - ELSE - AF ← 0; - FI; - IF ((old_AL > 99H) or old_CF = 1) - THEN - AL ← AL - 60H; - CF ← 1; - ELSE - CF ← 0; - FI; - */ - /* - The CF and AF flags are set if the adjustment of the value results in a - decimal carry in either digit of the result (see the “Operation” section above). - The SF, ZF, and PF flags are set according to the result. The OF flag is undefined. - */ - const uint8_t old_al = al; - const auto old_carry = context.flags.template flag(); - context.flags.template set_from(0); - - if((al & 0x0f) > 0x09 || context.flags.template flag()) { - context.flags.template set_from(old_carry | (al < 0x06)); - al -= 0x06; - context.flags.template set_from(1); - } else { - context.flags.template set_from(0); - } - - if(old_al > 0x99 || old_carry) { - al -= 0x60; - context.flags.template set_from(1); - } else { - context.flags.template set_from(0); - } - - context.flags.template set_from(al); -} - -template -void add( - modify_t destination, - read_t source, - ContextT &context -) { - /* - DEST ← DEST + SRC [+ CF]; - */ - /* - The OF, SF, ZF, AF, CF, and PF flags are set according to the result. - */ - const IntT result = destination + source + (with_carry ? context.flags.template carry_bit() : 0); - - context.flags.template set_from( - Numeric::carried_out() - 1>(destination, source, result)); - context.flags.template set_from( - Numeric::carried_in<4>(destination, source, result)); - context.flags.template set_from( - Numeric::overflow(destination, source, result)); - - context.flags.template set_from(result); - - destination = result; -} - -template -void sub( - access_t destination, - read_t source, - ContextT &context -) { - /* - DEST ← DEST - (SRC [+ CF]); - */ - /* - The OF, SF, ZF, AF, CF, and PF flags are set according to the result. - */ - const IntT result = destination - source - (with_borrow ? context.flags.template carry_bit() : 0); - - context.flags.template set_from( - Numeric::carried_out() - 1>(destination, source, result)); - context.flags.template set_from( - Numeric::carried_in<4>(destination, source, result)); - context.flags.template set_from( - Numeric::overflow(destination, source, result)); - - context.flags.template set_from(result); - - if constexpr (is_writeable(destination_type)) { - destination = result; - } -} - -template -void test( - read_t destination, - read_t source, - ContextT &context -) { - /* - TEMP ← SRC1 AND SRC2; - SF ← MSB(TEMP); - IF TEMP = 0 - THEN ZF ← 0; - ELSE ZF ← 1; - FI: - PF ← BitwiseXNOR(TEMP[0:7]); - CF ← 0; - OF ← 0; - */ - /* - The OF and CF flags are cleared to 0. - The SF, ZF, and PF flags are set according to the result (see the “Operation” section above). - The state of the AF flag is undefined. - */ - const IntT result = destination & source; - - context.flags.template set_from(0); - context.flags.template set_from(result); -} - -template -void xchg( - modify_t destination, - modify_t source -) { - /* - TEMP ← DEST - DEST ← SRC - SRC ← TEMP - */ - std::swap(destination, source); -} - -template -void mul( - modify_t destination_high, - modify_t destination_low, - read_t source, - ContextT &context -) { - /* - IF byte operation - THEN - AX ← AL * SRC - ELSE (* word or doubleword operation *) - IF OperandSize = 16 THEN - DX:AX ← AX * SRC - ELSE (* OperandSize = 32 *) - EDX:EAX ← EAX * SRC - FI; - */ - /* - The OF and CF flags are cleared to 0 if the upper half of the result is 0; - otherwise, they are set to 1. The SF, ZF, AF, and PF flags are undefined. - */ - destination_high = (destination_low * source) >> (8 * sizeof(IntT)); - destination_low *= source; - context.flags.template set_from(destination_high); -} - -template -void imul( - modify_t destination_high, - modify_t destination_low, - read_t source, - ContextT &context -) { - /* - (as modified by https://www.felixcloutier.com/x86/daa ...) - - IF (OperandSize = 8) - THEN - AX ← AL ∗ SRC (* signed multiplication *) - IF (AX = SignExtend(AL)) - THEN CF = 0; OF = 0; - ELSE CF = 1; OF = 1; - FI; - ELSE IF OperandSize = 16 - THEN - DX:AX ← AX ∗ SRC (* signed multiplication *) - IF (DX:AX = SignExtend(AX)) - THEN CF = 0; OF = 0; - ELSE CF = 1; OF = 1; - FI; - ELSE (* OperandSize = 32 *) - EDX:EAX ← EAX ∗ SRC (* signed multiplication *) - IF (EDX:EAX = SignExtend(EAX)) - THEN CF = 0; OF = 0; - ELSE CF = 1; OF = 1; - FI; - FI; - */ - using sIntT = typename std::make_signed::type; - destination_high = (sIntT(destination_low) * sIntT(source)) >> (8 * sizeof(IntT)); - destination_low = IntT(sIntT(destination_low) * sIntT(source)); - - const auto sign_extension = (destination_low & Numeric::top_bit()) ? IntT(~0) : 0; - context.flags.template set_from(destination_high != sign_extension); -} - -template -void div( - modify_t destination_high, - modify_t destination_low, - read_t source, - ContextT &context -) { - /* - IF SRC = 0 - THEN #DE; (* divide error *) - FI; - IF OperandSize = 8 (* word/byte operation *) - THEN - temp ← AX / SRC; - IF temp > FFH - THEN #DE; (* divide error *) ; - ELSE - AL ← temp; - AH ← AX MOD SRC; - FI; - ELSE - IF OperandSize = 16 (* doubleword/word operation *) - THEN - temp ← DX:AX / SRC; - IF temp > FFFFH - THEN #DE; (* divide error *) ; - ELSE - AX ← temp; - DX ← DX:AX MOD SRC; - FI; - ELSE (* quadword/doubleword operation *) - temp ← EDX:EAX / SRC; - IF temp > FFFFFFFFH - THEN #DE; (* divide error *) ; - ELSE - EAX ← temp; - EDX ← EDX:EAX MOD SRC; - FI; - FI; - FI; - */ - /* - The CF, OF, SF, ZF, AF, and PF flags are undefined. - */ - if(!source) { - interrupt(Interrupt::DivideError, context); - return; - } - - // TEMPORARY HACK. Will not work with DWords. - const uint32_t dividend = (destination_high << (8 * sizeof(IntT))) + destination_low; - const auto result = dividend / source; - if(IntT(result) != result) { - interrupt(Interrupt::DivideError, context); - return; - } - - destination_low = IntT(result); - destination_high = dividend % source; -} - -template -void idiv( - modify_t destination_high, - modify_t destination_low, - read_t source, - ContextT &context -) { - /* - IF SRC = 0 - THEN #DE; (* divide error *) - FI; - IF OperandSize = 8 (* word/byte operation *) - THEN - temp ← AX / SRC; (* signed division *) - IF (temp > 7FH) OR (temp < 80H) (* if a positive result is greater than 7FH or a negative result is less than 80H *) - THEN #DE; (* divide error *) ; - ELSE - AL ← temp; - AH ← AX MOD SRC; - FI; - ELSE - IF OperandSize = 16 (* doubleword/word operation *) - THEN - temp ← DX:AX / SRC; (* signed division *) - IF (temp > 7FFFH) OR (temp < 8000H) (* if a positive result is greater than 7FFFH or a negative result is less than 8000H *) - THEN #DE; (* divide error *) ; - ELSE - AX ← temp; - DX ← DX:AX MOD SRC; - FI; - ELSE (* quadword/doubleword operation *) - temp ← EDX:EAX / SRC; (* signed division *) - IF (temp > 7FFFFFFFH) OR (temp < 80000000H) (* if a positive result is greater than 7FFFFFFFH or a negative result is less than 80000000H *) - THEN #DE; (* divide error *) ; - ELSE - EAX ← temp; - EDX ← EDX:EAX MOD SRC; - FI; - FI; - FI; - */ - /* - The CF, OF, SF, ZF, AF, and PF flags are undefined. - */ - if(!source) { - interrupt(Interrupt::DivideError, context); - return; - } - - // TEMPORARY HACK. Will not work with DWords. - using sIntT = typename std::make_signed::type; - const int32_t dividend = (sIntT(destination_high) << (8 * sizeof(IntT))) + destination_low; - const auto result = dividend / sIntT(source); - if(sIntT(result) != result) { - interrupt(Interrupt::DivideError, context); - return; - } - - destination_low = IntT(result); - destination_high = dividend % sIntT(source); -} - -template -void inc( - modify_t destination, - ContextT &context -) { - /* - DEST ← DEST + 1; - */ - /* - The CF flag is not affected. - The OF, SF, ZF, AF, and PF flags are set according to the result. - */ - ++destination; - - context.flags.template set_from(destination == Numeric::top_bit()); - context.flags.template set_from(((destination - 1) ^ destination) & 0x10); - context.flags.template set_from(destination); -} - -template -void jump( - bool condition, - IntT displacement, - ContextT &context -) { - /* - IF condition - THEN - EIP ← EIP + SignExtend(DEST); - IF OperandSize = 16 - THEN - EIP ← EIP AND 0000FFFFH; - FI; - FI; - */ - - // TODO: proper behaviour in 32-bit. - if(condition) { - context.flow_controller.jump(context.registers.ip() + displacement); - } -} - -template -void loop( - modify_t counter, - OffsetT displacement, - ContextT &context -) { - --counter; - if(counter) { - context.flow_controller.jump(context.registers.ip() + displacement); - } -} - -template -void loope( - modify_t counter, - OffsetT displacement, - ContextT &context -) { - --counter; - if(counter && context.flags.template flag()) { - context.flow_controller.jump(context.registers.ip() + displacement); - } -} - -template -void loopne( - modify_t counter, - OffsetT displacement, - ContextT &context -) { - --counter; - if(counter && !context.flags.template flag()) { - context.flow_controller.jump(context.registers.ip() + displacement); - } -} - -template -void dec( - modify_t destination, - ContextT &context -) { - /* - DEST ← DEST - 1; - */ - /* - The CF flag is not affected. - The OF, SF, ZF, AF, and PF flags are set according to the result. - */ - context.flags.template set_from(destination == Numeric::top_bit()); - - --destination; - - context.flags.template set_from(destination); - context.flags.template set_from(((destination + 1) ^ destination) & 0x10); -} - -template -void and_( - modify_t destination, - read_t source, - ContextT &context -) { - /* - DEST ← DEST AND SRC; - */ - /* - The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. - The state of the AF flag is undefined. - */ - destination &= source; - - context.flags.template set_from(0); - context.flags.template set_from(destination); -} - -template -void or_( - modify_t destination, - read_t source, - ContextT &context -) { - /* - DEST ← DEST OR SRC; - */ - /* - The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. - The state of the AF flag is undefined. - */ - destination |= source; - - context.flags.template set_from(0); - context.flags.template set_from(destination); -} - -template -void xor_( - modify_t destination, - read_t source, - ContextT &context -) { - /* - DEST ← DEST XOR SRC; - */ - /* - The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. - The state of the AF flag is undefined. - */ - destination ^= source; - - context.flags.template set_from(0); - context.flags.template set_from(destination); -} - -template -void neg( - modify_t destination, - ContextT &context -) { - /* - IF DEST = 0 - THEN CF ← 0 - ELSE CF ← 1; - FI; - DEST ← –(DEST) - */ - /* - The CF flag cleared to 0 if the source operand is 0; otherwise it is set to 1. - The OF, SF, ZF, AF, and PF flags are set according to the result. - */ - context.flags.template set_from(Numeric::carried_in<4>(IntT(0), destination, IntT(-destination))); - - destination = -destination; - - context.flags.template set_from(destination); - context.flags.template set_from(destination == Numeric::top_bit()); - context.flags.template set_from(destination); -} - -template -void not_( - modify_t destination -) { - /* - DEST ← NOT DEST; - */ - /* - Flags affected: none. - */ - destination = ~destination; -} - -template -void call_relative( - IntT offset, - ContextT &context -) { - push(context.registers.ip(), context); - context.flow_controller.jump(context.registers.ip() + offset); -} - -template -void call_absolute( - read_t target, - ContextT &context -) { - push(context.registers.ip(), context); - context.flow_controller.jump(target); -} - -template -void jump_absolute( - read_t target, - ContextT &context -) { - context.flow_controller.jump(target); -} - -template -void call_far( - InstructionT &instruction, - ContextT &context -) { - // TODO: eliminate 16-bit assumption below. - const Source source_segment = instruction.data_segment(); - context.memory.preauthorise_stack_write(sizeof(uint16_t) * 2); - - uint16_t source_address; - const auto pointer = instruction.destination(); - switch(pointer.source()) { - default: - case Source::Immediate: - push(context.registers.cs(), context); - push(context.registers.ip(), context); - context.flow_controller.jump(instruction.segment(), instruction.offset()); - return; - - case Source::Indirect: - source_address = address(instruction, pointer, context); - break; - case Source::IndirectNoBase: - source_address = address(instruction, pointer, context); - break; - case Source::DirectAddress: - source_address = address(instruction, pointer, context); - break; - } - - context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); - const uint16_t offset = context.memory.template access(source_segment, source_address); - source_address += 2; - const uint16_t segment = context.memory.template access(source_segment, source_address); - - // At least on an 8086, the stack writes occur after the target address read. - push(context.registers.cs(), context); - push(context.registers.ip(), context); - - context.flow_controller.jump(segment, offset); -} - -template -void jump_far( - InstructionT &instruction, - ContextT &context -) { - // TODO: eliminate 16-bit assumption below. - uint16_t source_address = 0; - const auto pointer = instruction.destination(); - switch(pointer.source()) { - default: - case Source::Immediate: context.flow_controller.jump(instruction.segment(), instruction.offset()); return; - - case Source::Indirect: - source_address = address(instruction, pointer, context); - break; - case Source::IndirectNoBase: - source_address = address(instruction, pointer, context); - break; - case Source::DirectAddress: - source_address = address(instruction, pointer, context); - break; - } - - const Source source_segment = instruction.data_segment(); - context.memory.preauthorise_read(source_segment, source_address, sizeof(uint16_t) * 2); - - const uint16_t offset = context.memory.template access(source_segment, source_address); - source_address += 2; - const uint16_t segment = context.memory.template access(source_segment, source_address); - context.flow_controller.jump(segment, offset); -} - -template -void iret( - ContextT &context -) { - // TODO: all modes other than 16-bit real mode. - context.memory.preauthorise_stack_read(sizeof(uint16_t) * 3); - const auto ip = pop(context); - const auto cs = pop(context); - context.flags.set(pop(context)); - context.flow_controller.jump(cs, ip); -} - -template -void ret_near( - InstructionT instruction, - ContextT &context -) { - const auto ip = pop(context); - context.registers.sp() += instruction.operand(); - context.flow_controller.jump(ip); -} - -template -void ret_far( - InstructionT instruction, - ContextT &context -) { - context.memory.preauthorise_stack_read(sizeof(uint16_t) * 2); - const auto ip = pop(context); - const auto cs = pop(context); - context.registers.sp() += instruction.operand(); - context.flow_controller.jump(cs, ip); -} - -template -void ld( - const InstructionT &instruction, - write_t destination, - ContextT &context -) { - const auto pointer = instruction.source(); - auto source_address = address(instruction, pointer, context); - const Source source_segment = instruction.data_segment(); - - context.memory.preauthorise_read(source_segment, source_address, 4); - destination = context.memory.template access(source_segment, source_address); - source_address += 2; - switch(selector) { - case Source::DS: context.registers.ds() = context.memory.template access(source_segment, source_address); break; - case Source::ES: context.registers.es() = context.memory.template access(source_segment, source_address); break; - } -} - -template -void lea( - const InstructionT &instruction, - write_t destination, - ContextT &context -) { - // TODO: address size. - destination = IntT(address(instruction, instruction.source(), context)); -} - -template -void xlat( - const InstructionT &instruction, - ContextT &context -) { - AddressT address; - if constexpr (std::is_same_v) { - address = context.registers.bx() + context.registers.al(); - } - - context.registers.al() = context.memory.template access(instruction.data_segment(), address); -} - -template -void mov( - write_t destination, - read_t source -) { - destination = source; -} - -template -void into( - ContextT &context -) { - if(context.flags.template flag()) { - interrupt(Interrupt::OnOverflow, context); - } -} - -template -void sahf( - uint8_t &ah, - ContextT &context -) { - /* - EFLAGS(SF:ZF:0:AF:0:PF:1:CF) ← AH; - */ - context.flags.template set_from(ah); - context.flags.template set_from(!(ah & 0x40)); - context.flags.template set_from(ah & 0x10); - context.flags.template set_from(!(ah & 0x04)); - context.flags.template set_from(ah & 0x01); -} - -template -void lahf( - uint8_t &ah, - ContextT &context -) { - /* - AH ← EFLAGS(SF:ZF:0:AF:0:PF:1:CF); - */ - ah = - (context.flags.template flag() ? 0x80 : 0x00) | - (context.flags.template flag() ? 0x40 : 0x00) | - (context.flags.template flag() ? 0x10 : 0x00) | - (context.flags.template flag() ? 0x00 : 0x04) | - 0x02 | - (context.flags.template flag() ? 0x01 : 0x00); -} - -template -void cbw( - IntT &ax -) { - constexpr IntT test_bit = 1 << (sizeof(IntT) * 4 - 1); - constexpr IntT low_half = (1 << (sizeof(IntT) * 4)) - 1; - - if(ax & test_bit) { - ax |= ~low_half; - } else { - ax &= low_half; - } -} - -template -void cwd( - IntT &dx, - IntT ax -) { - dx = ax & Numeric::top_bit() ? IntT(~0) : IntT(0); -} - -// TODO: changes to the interrupt flag do quite a lot more in protected mode. -template -void clc(ContextT &context) { context.flags.template set_from(0); } -template -void cld(ContextT &context) { context.flags.template set_from(0); } -template -void cli(ContextT &context) { context.flags.template set_from(0); } -template -void stc(ContextT &context) { context.flags.template set_from(1); } -template -void std(ContextT &context) { context.flags.template set_from(1); } -template -void sti(ContextT &context) { context.flags.template set_from(1); } -template -void cmc(ContextT &context) { - context.flags.template set_from(!context.flags.template flag()); -} - -template -void salc( - uint8_t &al, - ContextT &context -) { - al = context.flags.template flag() ? 0xff : 0x00; -} - -template -void setmo( - write_t destination, - ContextT &context -) { - const auto result = destination = ~0; - context.flags.template set_from(0); - context.flags.template set_from(result); -} - -template -void rcl( - modify_t destination, - uint8_t count, - ContextT &context -) { - /* - (* RCL and RCR instructions *) - SIZE ← OperandSize - CASE (determine count) OF - SIZE = 8: tempCOUNT ← (COUNT AND 1FH) MOD 9; - SIZE = 16: tempCOUNT ← (COUNT AND 1FH) MOD 17; - SIZE = 32: tempCOUNT ← COUNT AND 1FH; - ESAC; - */ - /* - (* RCL instruction operation *) - WHILE (tempCOUNT ≠ 0) - DO - tempCF ← MSB(DEST); - DEST ← (DEST * 2) + CF; - CF ← tempCF; - tempCOUNT ← tempCOUNT – 1; - OD; - ELIHW; - IF COUNT = 1 - THEN OF ← MSB(DEST) XOR CF; - ELSE OF is undefined; - FI; - */ - /* - The CF flag contains the value of the bit shifted into it. - The OF flag is affected only for single- bit rotates (see “Description” above); - it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. - */ - const auto temp_count = count % (Numeric::bit_size() + 1); - auto carry = context.flags.template carry_bit(); - switch(temp_count) { - case 0: break; - case Numeric::bit_size(): { - const IntT temp_carry = destination & 1; - destination = (destination >> 1) | (carry << (Numeric::bit_size() - 1)); - carry = temp_carry; - } break; - default: { - const IntT temp_carry = destination & (Numeric::top_bit() >> (temp_count - 1)); - destination = - (destination << temp_count) | - (destination >> (Numeric::bit_size() + 1 - temp_count)) | - (carry << (temp_count - 1)); - carry = temp_carry ? 1 : 0; - } break; - } - - context.flags.template set_from(carry); - context.flags.template set_from( - ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry - ); -} - -template -void rcr( - modify_t destination, - uint8_t count, - ContextT &context -) { - /* - (* RCR instruction operation *) - IF COUNT = 1 - THEN OF ← MSB(DEST) XOR CF; - ELSE OF is undefined; - FI; - WHILE (tempCOUNT ≠ 0) - DO - tempCF ← LSB(SRC); - DEST ← (DEST / 2) + (CF * 2SIZE); - CF ← tempCF; - tempCOUNT ← tempCOUNT – 1; - OD; - */ - auto carry = context.flags.template carry_bit(); - context.flags.template set_from( - ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry - ); - - const auto temp_count = count % (Numeric::bit_size() + 1); - switch(temp_count) { - case 0: break; - case Numeric::bit_size(): { - const IntT temp_carry = destination & Numeric::top_bit(); - destination = (destination << 1) | carry; - carry = temp_carry; - } break; - default: { - const IntT temp_carry = destination & (1 << (temp_count - 1)); - destination = - (destination >> temp_count) | - (destination << (Numeric::bit_size() + 1 - temp_count)) | - (carry << (Numeric::bit_size() - temp_count)); - carry = temp_carry; - } break; - } - - context.flags.template set_from(carry); -} - -template -void rol( - modify_t destination, - uint8_t count, - ContextT &context -) { - /* - (* ROL and ROR instructions *) - SIZE ← OperandSize - CASE (determine count) OF - SIZE = 8: tempCOUNT ← COUNT MOD 8; - SIZE = 16: tempCOUNT ← COUNT MOD 16; - SIZE = 32: tempCOUNT ← COUNT MOD 32; - ESAC; - */ - /* - (* ROL instruction operation *) - WHILE (tempCOUNT ≠ 0) - DO - tempCF ← MSB(DEST); - DEST ← (DEST * 2) + tempCF; - tempCOUNT ← tempCOUNT – 1; - OD; - ELIHW; - IF COUNT = 1 - THEN OF ← MSB(DEST) XOR CF; - ELSE OF is undefined; - FI; - */ - /* - The CF flag contains the value of the bit shifted into it. - The OF flag is affected only for single- bit rotates (see “Description” above); - it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. - */ - const auto temp_count = count & (Numeric::bit_size() - 1); - if(!count) { - // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? - return; - } - if(temp_count) { - destination = - (destination << temp_count) | - (destination >> (Numeric::bit_size() - temp_count)); - } - - context.flags.template set_from(destination & 1); - context.flags.template set_from( - ((destination >> (Numeric::bit_size() - 1)) ^ destination) & 1 - ); -} - -template -void ror( - modify_t destination, - uint8_t count, - ContextT &context -) { - /* - (* ROL and ROR instructions *) - SIZE ← OperandSize - CASE (determine count) OF - SIZE = 8: tempCOUNT ← COUNT MOD 8; - SIZE = 16: tempCOUNT ← COUNT MOD 16; - SIZE = 32: tempCOUNT ← COUNT MOD 32; - ESAC; - */ - /* - (* ROR instruction operation *) - WHILE (tempCOUNT ≠ 0) - DO - tempCF ← LSB(DEST); - DEST ← (DEST / 2) + (tempCF * 2^SIZE); - tempCOUNT ← tempCOUNT – 1; - OD; - ELIHW; - IF COUNT = 1 - THEN OF ← MSB(DEST) XOR MSB - 1 (DEST); - ELSE OF is undefined; - FI; - */ - /* - The CF flag contains the value of the bit shifted into it. - The OF flag is affected only for single- bit rotates (see “Description” above); - it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. - */ - const auto temp_count = count & (Numeric::bit_size() - 1); - if(!count) { - // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? - return; - } - if(temp_count) { - destination = - (destination >> temp_count) | - (destination << (Numeric::bit_size() - temp_count)); - } - - context.flags.template set_from(destination & Numeric::top_bit()); - context.flags.template set_from( - (destination ^ (destination << 1)) & Numeric::top_bit() - ); -} - -/* - tempCOUNT ← (COUNT AND 1FH); - tempDEST ← DEST; - WHILE (tempCOUNT ≠ 0) - DO - IF instruction is SAL or SHL - THEN - CF ← MSB(DEST); - ELSE (* instruction is SAR or SHR *) - CF ← LSB(DEST); - FI; - IF instruction is SAL or SHL - THEN - DEST ← DEST ∗ 2; - ELSE - IF instruction is SAR - THEN - DEST ← DEST / 2 (*Signed divide, rounding toward negative infinity*); - ELSE (* instruction is SHR *) - DEST ← DEST / 2 ; (* Unsigned divide *); - FI; - FI; - tempCOUNT ← tempCOUNT – 1; - OD; - (* Determine overflow for the various instructions *) - IF COUNT = 1 - THEN - IF instruction is SAL or SHL - THEN - OF ← MSB(DEST) XOR CF; - ELSE - IF instruction is SAR - THEN - OF ← 0; - ELSE (* instruction is SHR *) - OF ← MSB(tempDEST); - FI; - FI; - ELSE - IF COUNT = 0 - THEN - All flags remain unchanged; - ELSE (* COUNT neither 1 or 0 *) - OF ← undefined; - FI; - FI; -*/ -/* - The CF flag contains the value of the last bit shifted out of the destination operand; - it is undefined for SHL and SHR instructions where the count is greater than or equal to - the size (in bits) of the destination operand. The OF flag is affected only for 1-bit shifts - (see “Description” above); otherwise, it is undefined. - - The SF, ZF, and PF flags are set according to the result. If the count is 0, the flags are not affected. - For a non-zero count, the AF flag is undefined. -*/ -template -void sal( - modify_t destination, - uint8_t count, - ContextT &context -) { - switch(count) { - case 0: return; - case Numeric::bit_size(): - context.flags.template set_from(destination & 1); - destination = 0; - break; - default: - if(count > Numeric::bit_size()) { - context.flags.template set_from(0); - destination = 0; - } else { - const auto mask = (Numeric::top_bit() >> (count - 1)); - context.flags.template set_from( - destination & mask - ); - context.flags.template set_from( - (destination ^ (destination << 1)) & mask - ); - destination <<= count; - } - break; - } - context.flags.template set_from(destination); -} - -template -void sar( - modify_t destination, - uint8_t count, - ContextT &context -) { - if(!count) { - return; - } - - const IntT sign = Numeric::top_bit() & destination; - if(count >= Numeric::bit_size()) { - destination = sign ? IntT(~0) : IntT(0); - context.flags.template set_from(sign); - } else { - const IntT mask = 1 << (count - 1); - context.flags.template set_from(destination & mask); - destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0); - } - context.flags.template set_from(0); - context.flags.template set_from(destination); -} - -template -void shr( - modify_t destination, - uint8_t count, - ContextT &context -) { - if(!count) { - return; - } - - context.flags.template set_from(Numeric::top_bit() & destination); - if(count == Numeric::bit_size()) { - context.flags.template set_from(Numeric::top_bit() & destination); - destination = 0; - } else if(count > Numeric::bit_size()) { - context.flags.template set_from(0); - destination = 0; - } else { - const IntT mask = 1 << (count - 1); - context.flags.template set_from(destination & mask); - destination >>= count; - } - context.flags.template set_from(destination); -} - -template -void popf( - ContextT &context -) { - context.flags.set(pop(context)); -} - -template -void pushf( - ContextT &context -) { - uint16_t value = context.flags.get(); - push(value, context); -} - -template -bool repetition_over( - const AddressT &eCX -) { - return repetition != Repetition::None && !eCX; -} - -template -void repeat( - AddressT &eCX, - ContextT &context -) { - if( - repetition == Repetition::None || // No repetition => stop. - !(--eCX) // [e]cx is zero after being decremented => stop. - ) { - return; - } - if constexpr (repetition != Repetition::Rep) { - // If this is RepE or RepNE, also test the zero flag. - if((repetition == Repetition::RepNE) == context.flags.template flag()) { - return; - } - } - context.flow_controller.repeat_last(); -} - -template -void cmps( - const InstructionT &instruction, - AddressT &eCX, - AddressT &eSI, - AddressT &eDI, - ContextT &context -) { - if(repetition_over(eCX)) { - return; - } - - IntT lhs = context.memory.template access(instruction.data_segment(), eSI); - const IntT rhs = context.memory.template access(Source::ES, eDI); - eSI += context.flags.template direction() * sizeof(IntT); - eDI += context.flags.template direction() * sizeof(IntT); - - Primitive::sub(lhs, rhs, context); - - repeat(eCX, context); -} - -template -void scas(AddressT &eCX, AddressT &eDI, IntT &eAX, ContextT &context) { - if(repetition_over(eCX)) { - return; - } - - const IntT rhs = context.memory.template access(Source::ES, eDI); - eDI += context.flags.template direction() * sizeof(IntT); - - Primitive::sub(eAX, rhs, context); - - repeat(eCX, context); -} - -template -void lods(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, IntT &eAX, ContextT &context) { - if(repetition_over(eCX)) { - return; - } - - eAX = context.memory.template access(instruction.data_segment(), eSI); - eSI += context.flags.template direction() * sizeof(IntT); - - repeat(eCX, context); -} - -template -void movs(const InstructionT &instruction, AddressT &eCX, AddressT &eSI, AddressT &eDI, ContextT &context) { - if(repetition_over(eCX)) { - return; - } - - context.memory.template access(Source::ES, eDI) = - context.memory.template access(instruction.data_segment(), eSI); - - eSI += context.flags.template direction() * sizeof(IntT); - eDI += context.flags.template direction() * sizeof(IntT); - - repeat(eCX, context); -} - -template -void stos(AddressT &eCX, AddressT &eDI, IntT &eAX, ContextT &context) { - if(repetition_over(eCX)) { - return; - } - - context.memory.template access(Source::ES, eDI) = eAX; - eDI += context.flags.template direction() * sizeof(IntT); - - repeat(eCX, context); -} - -template -void outs(const InstructionT &instruction, AddressT &eCX, uint16_t port, AddressT &eSI, ContextT &context) { - if(repetition_over(eCX)) { - return; - } - - context.io.template out( - port, - context.memory.template access(instruction.data_segment(), eSI) - ); - eSI += context.flags.template direction() * sizeof(IntT); - - repeat(eCX, context); -} - -template -void ins(AddressT &eCX, uint16_t port, AddressT &eDI, ContextT &context) { - if(repetition_over(eCX)) { - return; - } - - context.memory.template access(Source::ES, eDI) = context.io.template in(port); - eDI += context.flags.template direction() * sizeof(IntT); - - repeat(eCX, context); -} - -template -void out(uint16_t port, IntT value, ContextT &context) { - context.io.template out(port, value); -} - -template -void in(uint16_t port, IntT &value, ContextT &context) { - value = context.io.template in(port); -} - -} +namespace InstructionSet::x86 { template < DataSize data_size, @@ -1648,13 +165,16 @@ template < // Guide to the below: // - // * use hard-coded register names where appropriate; + // * 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; - // * otherwise use the source() and destination() lambdas, and break in order to allow a writeback if necessary. + // * break if there's a chance of writeback. switch(instruction.operation()) { default: assert(false); + 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; @@ -1665,14 +185,15 @@ template < case Operation::CBW: Primitive::cbw(pair_low()); return; case Operation::CWD: Primitive::cwd(pair_high(), pair_low()); return; - case Operation::ESC: - case Operation::NOP: return; - case Operation::HLT: context.flow_controller.halt(); return; case Operation::WAIT: context.flow_controller.wait(); return; - case Operation::ADC: Primitive::add(destination_rmw(), source_r(), context); break; - case Operation::ADD: Primitive::add(destination_rmw(), source_r(), context); break; + case Operation::ADC: + Primitive::add(destination_rmw(), source_r(), context); + break; + case Operation::ADD: + Primitive::add(destination_rmw(), source_r(), context); + break; case Operation::SBB: Primitive::sub(destination_rmw(), source_r(), context); break; @@ -1682,7 +203,9 @@ template < case Operation::CMP: Primitive::sub(destination_r(), source_r(), context); return; - case Operation::TEST: Primitive::test(destination_r(), source_r(), context); return; + case Operation::TEST: + Primitive::test(destination_r(), source_r(), context); + return; case Operation::MUL: Primitive::mul(pair_high(), pair_low(), source_r(), context); return; case Operation::IMUL_1: Primitive::imul(pair_high(), pair_low(), source_r(), context); return; @@ -1721,8 +244,12 @@ template < case Operation::SAHF: Primitive::sahf(context.registers.ah(), context); return; case Operation::LAHF: Primitive::lahf(context.registers.ah(), context); return; - case Operation::LDS: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), context); return; - case Operation::LES: if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), context); return; + case Operation::LDS: + if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), context); + return; + case Operation::LES: + if constexpr (data_size == DataSize::Word) Primitive::ld(instruction, destination_w(), context); + return; case Operation::LEA: Primitive::lea(instruction, destination_w(), context); return; case Operation::MOV: Primitive::mov(destination_w(), source_r()); break; @@ -1768,7 +295,8 @@ template < Primitive::setmo(destination_w(), context); break; } else { - // TODO. + // TODO: perform ENTER as of the 80186. + static_assert(int(Operation::SETMO) == int(Operation::ENTER)); } return; case Operation::SETMOC: @@ -1780,12 +308,13 @@ template < } break; } else { - // TODO. + // TODO: perform BOUND as of the 80186. + static_assert(int(Operation::SETMOC) == int(Operation::BOUND)); } return; - case Operation::OUT: Primitive::out(port(instruction.destination().source()), pair_low(), context); return; - case Operation::IN: Primitive::in(port(instruction.source().source()), pair_low(), context); return; + case Operation::OUT: Primitive::out(port(instruction.destination().source()), pair_low(), context); return; + case Operation::IN: Primitive::in(port(instruction.source().source()), pair_low(), context); return; case Operation::XLAT: Primitive::xlat(instruction, context); return; @@ -1794,35 +323,35 @@ template < Primitive::push(source_rmw(), context); // PUSH SP modifies SP before pushing it; // hence PUSH is sometimes read-modify-write. break; - case Operation::POPF: Primitive::popf(context); break; - case Operation::PUSHF: Primitive::pushf(context); break; + case Operation::POPF: Primitive::popf(context); return; + case Operation::PUSHF: Primitive::pushf(context); return; case Operation::CMPS: Primitive::cmps(instruction, eCX(), eSI(), eDI(), context); - break; + return; case Operation::CMPS_REPE: Primitive::cmps(instruction, eCX(), eSI(), eDI(), context); - break; + return; case Operation::CMPS_REPNE: Primitive::cmps(instruction, eCX(), eSI(), eDI(), context); - break; + return; case Operation::SCAS: Primitive::scas(eCX(), eDI(), pair_low(), context); - break; + return; case Operation::SCAS_REPE: Primitive::scas(eCX(), eDI(), pair_low(), context); - break; + return; case Operation::SCAS_REPNE: Primitive::scas(eCX(), eDI(), pair_low(), context); - break; + return; case Operation::LODS: Primitive::lods(instruction, eCX(), eSI(), pair_low(), context); - break; + return; case Operation::LODS_REP: Primitive::lods(instruction, eCX(), eSI(), pair_low(), context); - break; + return; case Operation::MOVS: Primitive::movs(instruction, eCX(), eSI(), eDI(), context); @@ -1840,10 +369,10 @@ template < case Operation::OUTS: Primitive::outs(instruction, eCX(), context.registers.dx(), eSI(), context); - break; + return; case Operation::OUTS_REP: Primitive::outs(instruction, eCX(), context.registers.dx(), eSI(), context); - break; + return; case Operation::INS: Primitive::ins(eCX(), context.registers.dx(), eDI(), context); @@ -1855,10 +384,19 @@ template < // Write to memory if required to complete this operation. // - // TODO: can I eliminate this with some RAII magic? + // 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(); } +// +// 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 < typename InstructionT, typename ContextT diff --git a/InstructionSets/x86/Implementation/Repetition.hpp b/InstructionSets/x86/Implementation/Repetition.hpp new file mode 100644 index 000000000..65954c7fa --- /dev/null +++ b/InstructionSets/x86/Implementation/Repetition.hpp @@ -0,0 +1,180 @@ +// +// Repetition.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Repetition_h +#define Repetition_h + +#include "../AccessType.hpp" + +namespace InstructionSet::x86::Primitive { + +template +bool repetition_over( + const AddressT &eCX +) { + return repetition != Repetition::None && !eCX; +} + +template +void repeat( + AddressT &eCX, + ContextT &context +) { + if( + repetition == Repetition::None || // No repetition => stop. + !(--eCX) // [e]cx is zero after being decremented => stop. + ) { + return; + } + if constexpr (repetition != Repetition::Rep) { + // If this is RepE or RepNE, also test the zero flag. + if((repetition == Repetition::RepNE) == context.flags.template flag()) { + return; + } + } + context.flow_controller.repeat_last(); +} + +template +void cmps( + const InstructionT &instruction, + AddressT &eCX, + AddressT &eSI, + AddressT &eDI, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + IntT lhs = context.memory.template access(instruction.data_segment(), eSI); + const IntT rhs = context.memory.template access(Source::ES, eDI); + eSI += context.flags.template direction() * sizeof(IntT); + eDI += context.flags.template direction() * sizeof(IntT); + + Primitive::sub(lhs, rhs, context); + + repeat(eCX, context); +} + +template +void scas( + AddressT &eCX, + AddressT &eDI, + IntT &eAX, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + const IntT rhs = context.memory.template access(Source::ES, eDI); + eDI += context.flags.template direction() * sizeof(IntT); + + Primitive::sub(eAX, rhs, context); + + repeat(eCX, context); +} + +template +void lods( + const InstructionT &instruction, + AddressT &eCX, + AddressT &eSI, + IntT &eAX, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + eAX = context.memory.template access(instruction.data_segment(), eSI); + eSI += context.flags.template direction() * sizeof(IntT); + + repeat(eCX, context); +} + +template +void movs( + const InstructionT &instruction, + AddressT &eCX, + AddressT &eSI, + AddressT &eDI, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + context.memory.template access(Source::ES, eDI) = + context.memory.template access(instruction.data_segment(), eSI); + + eSI += context.flags.template direction() * sizeof(IntT); + eDI += context.flags.template direction() * sizeof(IntT); + + repeat(eCX, context); +} + +template +void stos( + AddressT &eCX, + AddressT &eDI, + IntT &eAX, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + context.memory.template access(Source::ES, eDI) = eAX; + eDI += context.flags.template direction() * sizeof(IntT); + + repeat(eCX, context); +} + +template +void outs( + const InstructionT &instruction, + AddressT &eCX, + uint16_t port, + AddressT &eSI, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + context.io.template out( + port, + context.memory.template access(instruction.data_segment(), eSI) + ); + eSI += context.flags.template direction() * sizeof(IntT); + + repeat(eCX, context); +} + +template +void ins( + AddressT &eCX, + uint16_t port, + AddressT &eDI, + ContextT &context +) { + if(repetition_over(eCX)) { + return; + } + + context.memory.template access(Source::ES, eDI) = context.io.template in(port); + eDI += context.flags.template direction() * sizeof(IntT); + + repeat(eCX, context); +} + +} + +#endif /* Repetition_h */ diff --git a/InstructionSets/x86/Implementation/ShiftRoll.hpp b/InstructionSets/x86/Implementation/ShiftRoll.hpp new file mode 100644 index 000000000..abf57ca82 --- /dev/null +++ b/InstructionSets/x86/Implementation/ShiftRoll.hpp @@ -0,0 +1,361 @@ +// +// ShiftRoll.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef ShiftRoll_hpp +#define ShiftRoll_hpp + +#include "../AccessType.hpp" + +namespace InstructionSet::x86::Primitive { + +template +void rcl( + modify_t destination, + uint8_t count, + ContextT &context +) { + /* + (* RCL and RCR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← (COUNT AND 1FH) MOD 9; + SIZE = 16: tempCOUNT ← (COUNT AND 1FH) MOD 17; + SIZE = 32: tempCOUNT ← COUNT AND 1FH; + ESAC; + */ + /* + (* RCL instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← MSB(DEST); + DEST ← (DEST * 2) + CF; + CF ← tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count % (Numeric::bit_size() + 1); + auto carry = context.flags.template carry_bit(); + switch(temp_count) { + case 0: break; + case Numeric::bit_size(): { + const IntT temp_carry = destination & 1; + destination = (destination >> 1) | (carry << (Numeric::bit_size() - 1)); + carry = temp_carry; + } break; + default: { + const IntT temp_carry = destination & (Numeric::top_bit() >> (temp_count - 1)); + destination = + (destination << temp_count) | + (destination >> (Numeric::bit_size() + 1 - temp_count)) | + (carry << (temp_count - 1)); + carry = temp_carry ? 1 : 0; + } break; + } + + context.flags.template set_from(carry); + context.flags.template set_from( + ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry + ); +} + +template +void rcr( + modify_t destination, + uint8_t count, + ContextT &context +) { + /* + (* RCR instruction operation *) + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← LSB(SRC); + DEST ← (DEST / 2) + (CF * 2SIZE); + CF ← tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + */ + auto carry = context.flags.template carry_bit(); + context.flags.template set_from( + ((destination >> (Numeric::bit_size() - 1)) & 1) ^ carry + ); + + const auto temp_count = count % (Numeric::bit_size() + 1); + switch(temp_count) { + case 0: break; + case Numeric::bit_size(): { + const IntT temp_carry = destination & Numeric::top_bit(); + destination = (destination << 1) | carry; + carry = temp_carry; + } break; + default: { + const IntT temp_carry = destination & (1 << (temp_count - 1)); + destination = + (destination >> temp_count) | + (destination << (Numeric::bit_size() + 1 - temp_count)) | + (carry << (Numeric::bit_size() - temp_count)); + carry = temp_carry; + } break; + } + + context.flags.template set_from(carry); +} + +template +void rol( + modify_t destination, + uint8_t count, + ContextT &context +) { + /* + (* ROL and ROR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← COUNT MOD 8; + SIZE = 16: tempCOUNT ← COUNT MOD 16; + SIZE = 32: tempCOUNT ← COUNT MOD 32; + ESAC; + */ + /* + (* ROL instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← MSB(DEST); + DEST ← (DEST * 2) + tempCF; + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR CF; + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count & (Numeric::bit_size() - 1); + if(!count) { + // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? + return; + } + if(temp_count) { + destination = + (destination << temp_count) | + (destination >> (Numeric::bit_size() - temp_count)); + } + + context.flags.template set_from(destination & 1); + context.flags.template set_from( + ((destination >> (Numeric::bit_size() - 1)) ^ destination) & 1 + ); +} + +template +void ror( + modify_t destination, + uint8_t count, + ContextT &context +) { + /* + (* ROL and ROR instructions *) + SIZE ← OperandSize + CASE (determine count) OF + SIZE = 8: tempCOUNT ← COUNT MOD 8; + SIZE = 16: tempCOUNT ← COUNT MOD 16; + SIZE = 32: tempCOUNT ← COUNT MOD 32; + ESAC; + */ + /* + (* ROR instruction operation *) + WHILE (tempCOUNT ≠ 0) + DO + tempCF ← LSB(DEST); + DEST ← (DEST / 2) + (tempCF * 2^SIZE); + tempCOUNT ← tempCOUNT – 1; + OD; + ELIHW; + IF COUNT = 1 + THEN OF ← MSB(DEST) XOR MSB - 1 (DEST); + ELSE OF is undefined; + FI; + */ + /* + The CF flag contains the value of the bit shifted into it. + The OF flag is affected only for single- bit rotates (see “Description” above); + it is undefined for multi-bit rotates. The SF, ZF, AF, and PF flags are not affected. + */ + const auto temp_count = count & (Numeric::bit_size() - 1); + if(!count) { + // TODO: is this 8086-specific? i.e. do the other x86s also exit without affecting flags when temp_count = 0? + return; + } + if(temp_count) { + destination = + (destination >> temp_count) | + (destination << (Numeric::bit_size() - temp_count)); + } + + context.flags.template set_from(destination & Numeric::top_bit()); + context.flags.template set_from( + (destination ^ (destination << 1)) & Numeric::top_bit() + ); +} + +/* + tempCOUNT ← (COUNT AND 1FH); + tempDEST ← DEST; + WHILE (tempCOUNT ≠ 0) + DO + IF instruction is SAL or SHL + THEN + CF ← MSB(DEST); + ELSE (* instruction is SAR or SHR *) + CF ← LSB(DEST); + FI; + IF instruction is SAL or SHL + THEN + DEST ← DEST ∗ 2; + ELSE + IF instruction is SAR + THEN + DEST ← DEST / 2 (*Signed divide, rounding toward negative infinity*); + ELSE (* instruction is SHR *) + DEST ← DEST / 2 ; (* Unsigned divide *); + FI; + FI; + tempCOUNT ← tempCOUNT – 1; + OD; + (* Determine overflow for the various instructions *) + IF COUNT = 1 + THEN + IF instruction is SAL or SHL + THEN + OF ← MSB(DEST) XOR CF; + ELSE + IF instruction is SAR + THEN + OF ← 0; + ELSE (* instruction is SHR *) + OF ← MSB(tempDEST); + FI; + FI; + ELSE + IF COUNT = 0 + THEN + All flags remain unchanged; + ELSE (* COUNT neither 1 or 0 *) + OF ← undefined; + FI; + FI; +*/ +/* + The CF flag contains the value of the last bit shifted out of the destination operand; + it is undefined for SHL and SHR instructions where the count is greater than or equal to + the size (in bits) of the destination operand. The OF flag is affected only for 1-bit shifts + (see “Description” above); otherwise, it is undefined. + + The SF, ZF, and PF flags are set according to the result. If the count is 0, the flags are not affected. + For a non-zero count, the AF flag is undefined. +*/ +template +void sal( + modify_t destination, + uint8_t count, + ContextT &context +) { + switch(count) { + case 0: return; + case Numeric::bit_size(): + context.flags.template set_from(destination & 1); + destination = 0; + break; + default: + if(count > Numeric::bit_size()) { + context.flags.template set_from(0); + destination = 0; + } else { + const auto mask = (Numeric::top_bit() >> (count - 1)); + context.flags.template set_from( + destination & mask + ); + context.flags.template set_from( + (destination ^ (destination << 1)) & mask + ); + destination <<= count; + } + break; + } + context.flags.template set_from(destination); +} + +template +void sar( + modify_t destination, + uint8_t count, + ContextT &context +) { + if(!count) { + return; + } + + const IntT sign = Numeric::top_bit() & destination; + if(count >= Numeric::bit_size()) { + destination = sign ? IntT(~0) : IntT(0); + context.flags.template set_from(sign); + } else { + const IntT mask = 1 << (count - 1); + context.flags.template set_from(destination & mask); + destination = (destination >> count) | (sign ? ~(IntT(~0) >> count) : 0); + } + context.flags.template set_from(0); + context.flags.template set_from(destination); +} + +template +void shr( + modify_t destination, + uint8_t count, + ContextT &context +) { + if(!count) { + return; + } + + context.flags.template set_from(Numeric::top_bit() & destination); + if(count == Numeric::bit_size()) { + context.flags.template set_from(Numeric::top_bit() & destination); + destination = 0; + } else if(count > Numeric::bit_size()) { + context.flags.template set_from(0); + destination = 0; + } else { + const IntT mask = 1 << (count - 1); + context.flags.template set_from(destination & mask); + destination >>= count; + } + context.flags.template set_from(destination); +} + +} + +#endif /* ShiftRoll_hpp */ diff --git a/InstructionSets/x86/Implementation/Stack.hpp b/InstructionSets/x86/Implementation/Stack.hpp new file mode 100644 index 000000000..b24619a15 --- /dev/null +++ b/InstructionSets/x86/Implementation/Stack.hpp @@ -0,0 +1,94 @@ +// +// Stack.hpp +// Clock Signal +// +// Created by Thomas Harte on 08/11/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Stack_hpp +#define Stack_hpp + +#include "../AccessType.hpp" + +namespace InstructionSet::x86::Primitive { + +// The below takes a reference in order properly to handle PUSH SP, +// which should place the value of SP after the push onto the stack. +template +void push( + IntT &value, + ContextT &context +) { + context.registers.sp_ -= sizeof(IntT); + if constexpr (preauthorised) { + context.memory.template preauthorised_write(Source::SS, context.registers.sp_, value); + } else { + context.memory.template access( + Source::SS, + context.registers.sp_) = value; + } + context.memory.template write_back(); +} + +template +IntT pop( + ContextT &context +) { + const auto value = context.memory.template access( + Source::SS, + context.registers.sp_); + context.registers.sp_ += sizeof(IntT); + return value; +} + +template +void sahf( + uint8_t &ah, + ContextT &context +) { + /* + EFLAGS(SF:ZF:0:AF:0:PF:1:CF) ← AH; + */ + context.flags.template set_from(ah); + context.flags.template set_from(!(ah & 0x40)); + context.flags.template set_from(ah & 0x10); + context.flags.template set_from(!(ah & 0x04)); + context.flags.template set_from(ah & 0x01); +} + +template +void lahf( + uint8_t &ah, + ContextT &context +) { + /* + AH ← EFLAGS(SF:ZF:0:AF:0:PF:1:CF); + */ + ah = + (context.flags.template flag() ? 0x80 : 0x00) | + (context.flags.template flag() ? 0x40 : 0x00) | + (context.flags.template flag() ? 0x10 : 0x00) | + (context.flags.template flag() ? 0x00 : 0x04) | + 0x02 | + (context.flags.template flag() ? 0x01 : 0x00); +} + +template +void popf( + ContextT &context +) { + context.flags.set(pop(context)); +} + +template +void pushf( + ContextT &context +) { + uint16_t value = context.flags.get(); + push(value, context); +} + +} + +#endif /* Stack_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 647a359ef..248e9fc01 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1129,6 +1129,15 @@ 42437B352ACF0AA2006DFED1 /* Perform.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Perform.hpp; sourceTree = ""; }; 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PerformImplementation.hpp; sourceTree = ""; }; 42437B392AD07465006DFED1 /* Interrupts.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Interrupts.hpp; sourceTree = ""; }; + 425739282AFBDF2700B7D1E4 /* Arithmetic.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Arithmetic.hpp; sourceTree = ""; }; + 425739292AFBE03600B7D1E4 /* Logical.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Logical.hpp; sourceTree = ""; }; + 4257392A2AFBE0AE00B7D1E4 /* ShiftRoll.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ShiftRoll.hpp; sourceTree = ""; }; + 4257392B2AFBE16000B7D1E4 /* FlowControl.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FlowControl.hpp; sourceTree = ""; }; + 4257392C2AFBE18900B7D1E4 /* Stack.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Stack.hpp; sourceTree = ""; }; + 4257392D2AFBE27C00B7D1E4 /* BCD.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BCD.hpp; sourceTree = ""; }; + 4257392E2AFBE2BC00B7D1E4 /* Repetition.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Repetition.hpp; sourceTree = ""; }; + 4257392F2AFBE36B00B7D1E4 /* LoadStore.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LoadStore.hpp; sourceTree = ""; }; + 425739302AFBE47700B7D1E4 /* InOut.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InOut.hpp; sourceTree = ""; }; 4281572E2AA0334300E16AA1 /* Carry.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Carry.hpp; sourceTree = ""; }; 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; @@ -2329,8 +2338,17 @@ 42437B372ACF2798006DFED1 /* Implementation */ = { isa = PBXGroup; children = ( + 425739282AFBDF2700B7D1E4 /* Arithmetic.hpp */, + 4257392D2AFBE27C00B7D1E4 /* BCD.hpp */, + 4257392B2AFBE16000B7D1E4 /* FlowControl.hpp */, + 425739302AFBE47700B7D1E4 /* InOut.hpp */, + 4257392F2AFBE36B00B7D1E4 /* LoadStore.hpp */, + 425739292AFBE03600B7D1E4 /* Logical.hpp */, 42437B382ACF2798006DFED1 /* PerformImplementation.hpp */, + 4257392E2AFBE2BC00B7D1E4 /* Repetition.hpp */, 42AA41242AF8893F0016751C /* Resolver.hpp */, + 4257392A2AFBE0AE00B7D1E4 /* ShiftRoll.hpp */, + 4257392C2AFBE18900B7D1E4 /* Stack.hpp */, ); path = Implementation; sourceTree = "";