1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-16 18:30:32 +00:00

Slims the Program struct down to 8 bytes total.

This commit is contained in:
Thomas Harte 2019-07-24 22:02:50 -04:00
parent 827c4e172a
commit 1327de1c82
4 changed files with 60 additions and 42 deletions

View File

@ -13,6 +13,7 @@
#include <cstring>
#include <iomanip>
#include <iostream>
#include <limits>
#include <ostream>
#include <vector>

View File

@ -316,8 +316,8 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
// should_log = (fetched_pc >= 0x408D66) && (fetched_pc <= 0x408D84);
#endif
if(instructions[decoded_instruction_.full].micro_operations) {
if(instructions[decoded_instruction_.full].requires_supervisor && !is_supervisor_) {
if(instructions[decoded_instruction_.full].micro_operations != std::numeric_limits<uint32_t>::max()) {
if((instructions[decoded_instruction_.full].source_dest & 0x80) && !is_supervisor_) {
// A privilege violation has been detected.
active_program_ = nullptr;
active_micro_op_ = short_exception_micro_ops_;
@ -325,7 +325,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
} else {
// Standard instruction dispatch.
active_program_ = &instructions[decoded_instruction_.full];
active_micro_op_ = active_program_->micro_operations;
active_micro_op_ = &all_micro_ops_[active_program_->micro_operations];
}
} else {
// The opcode fetched isn't valid.
@ -364,10 +364,11 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
case int(MicroOp::Action::None): break;
#define source() active_program_->source
#define source_address() address_[active_program_->source_dest >> 4]
#define destination() active_program_->destination
#define destination_address() address_[active_program_->source_dest & 0x0f]
#define offset_pointer(x) reinterpret_cast<RegisterPair32 *>(&reinterpret_cast<uint8_t *>(static_cast<ProcessorStorage *>(this))[x])
#define source() offset_pointer(active_program_->source_offset)
#define source_address() address_[(active_program_->source_dest >> 4) & 7]
#define destination() offset_pointer(active_program_->destination_offset)
#define destination_address() address_[active_program_->source_dest & 7]
case int(MicroOp::Action::PerformOperation):
#define sub_overflow() ((result ^ destination) & (destination ^ source))

View File

@ -867,7 +867,7 @@ struct ProcessorStorageConstructor {
switch(mapping.decoder) {
case Decoder::STOP: {
program.requires_supervisor = true;
program.set_requires_supervisor(true);
op(Action::None, seq("n"));
op(Action::PerformOperation);
} break;
@ -973,7 +973,7 @@ struct ProcessorStorageConstructor {
case Decoder::EORI_ORI_ANDI_SR: {
// The source used here is always the high word of the prefetch queue.
program.requires_supervisor = !!(instruction & 0x40);
program.set_requires_supervisor(!!(instruction & 0x40));
op(Action::None, seq("np nn nn"));
op(Action::PerformOperation, seq("np np"));
} break;
@ -1011,7 +1011,7 @@ struct ProcessorStorageConstructor {
} break;
case Decoder::RTE_RTR: {
program.requires_supervisor = instruction == 0x4e73;
program.set_requires_supervisor(instruction == 0x4e73);
// TODO: something explicit to ensure the nR nr nr is exclusively linked.
op(Action::PrepareRTE_RTR, seq("nR nr nr", { &storage_.precomputed_addresses_[0], &storage_.precomputed_addresses_[1], &storage_.precomputed_addresses_[2] } ));
@ -2082,12 +2082,12 @@ struct ProcessorStorageConstructor {
break;
case bw(Imm): // CMP.br #, Dn
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::PerformOperation, seq("np np"));
break;
case l(Imm): // CMP.l #, Dn
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::None, seq("np"));
op(Action::PerformOperation, seq("np np n"));
break;
@ -2164,12 +2164,12 @@ struct ProcessorStorageConstructor {
break;
case bw(Imm): // CMPA.w #, An
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::PerformOperation, seq("np np n"));
break;
case l(Imm): // CMPA.l #, An
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::None, seq("np"));
op(Action::PerformOperation, seq("np np n"));
break;
@ -2190,12 +2190,12 @@ struct ProcessorStorageConstructor {
default: continue;
case bw(Dn): // CMPI.bw #, Dn
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::PerformOperation, seq("np np"));
break;
case l(Dn): // CMPI.l #, Dn
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(Action::None, seq("np"));
op(Action::PerformOperation, seq("np np n"));
break;
@ -2363,7 +2363,7 @@ struct ProcessorStorageConstructor {
program.set_source(storage_, ea_mode, ea_register);
// ... but otherwise assume that the true source of a destination will be the computed source address.
program.source = &storage_.effective_address_[0];
program.set_source(storage_, &storage_.effective_address_[0]);
const int mode = combined_mode(ea_mode, ea_register);
switch(mode) {
@ -2475,10 +2475,10 @@ struct ProcessorStorageConstructor {
const int mode = combined_mode(ea_mode, ea_register);
program.set_source_address(storage_, ea_register);
program.source =
program.set_source(storage_,
(mode == Ind) ?
&storage_.address_[ea_register] :
&storage_.effective_address_[0];
&storage_.effective_address_[0]);
switch(mode) {
default: continue;
@ -2544,7 +2544,7 @@ struct ProcessorStorageConstructor {
case Decoder::MOVEtoSRCCR: {
if(ea_mode == An) continue;
program.set_source(storage_, ea_mode, ea_register);
program.requires_supervisor = (operation == Operation::MOVEtoSR);
program.set_requires_supervisor(operation == Operation::MOVEtoSR);
/* DEVIATION FROM YACHT.TXT: it has all of these reading an extra word from the PC;
this looks like a mistake so I've padded with nil cycles in the middle. */
@ -2582,7 +2582,7 @@ struct ProcessorStorageConstructor {
break;
case Imm: // MOVE #, SR
program.source = &storage_.prefetch_queue_;
program.set_source(storage_, &storage_.prefetch_queue_);
op(int(Action::PerformOperation), seq("np nn nn np"));
break;
}
@ -2670,18 +2670,18 @@ struct ProcessorStorageConstructor {
} break;
case Decoder::MOVEUSP: {
program.requires_supervisor = true;
program.set_requires_supervisor(true);
// Observation here: because this is a privileged instruction, the user stack pointer
// definitely isn't currently [copied into] A7.
if(instruction & 0x8) {
// Transfer FROM the USP.
program.source = &storage_.stack_pointers_[0];
program.set_source(storage_, &storage_.stack_pointers_[0]);
program.set_destination(storage_, An, ea_register);
} else {
// Transfer TO the USP.
program.set_source(storage_, An, ea_register);
program.destination = &storage_.stack_pointers_[0];
program.set_destination(storage_, &storage_.stack_pointers_[0]);
}
op(Action::PerformOperation, seq("np"));
@ -2869,7 +2869,7 @@ struct ProcessorStorageConstructor {
} break;
case Decoder::RESET:
program.requires_supervisor = true;
program.set_requires_supervisor(true);
op(Action::None, seq("nn _ np"));
break;
@ -3091,8 +3091,8 @@ struct ProcessorStorageConstructor {
// Finalise micro-op and program pointers.
for(size_t instruction = 0; instruction < 65536; ++instruction) {
if(micro_op_pointers[instruction] != std::numeric_limits<size_t>::max()) {
storage_.instructions[instruction].micro_operations = &storage_.all_micro_ops_[micro_op_pointers[instruction]];
link_operations(storage_.instructions[instruction].micro_operations, &arbitrary_base);
storage_.instructions[instruction].micro_operations = uint32_t(micro_op_pointers[instruction]);
link_operations(&storage_.all_micro_ops_[micro_op_pointers[instruction]], &arbitrary_base);
}
}
@ -3100,7 +3100,8 @@ struct ProcessorStorageConstructor {
storage_.interrupt_micro_ops_ = &storage_.all_micro_ops_[interrupt_pointer];
link_operations(storage_.interrupt_micro_ops_, &arbitrary_base);
std::cout << storage_.all_bus_steps_.size() << " total steps" << std::endl;
std::cout << storage_.all_bus_steps_.size() << " total bus steps" << std::endl;
std::cout << storage_.all_micro_ops_.size() << " total micro ops" << std::endl;
}
private:
@ -3248,7 +3249,7 @@ CPU::MC68000::ProcessorStorage::ProcessorStorage() {
//
// Assumed order of input: PC.h, SR, PC.l (i.e. the opposite of TRAP's output).
for(const int instruction: { 0x4e73, 0x4e77 }) {
auto steps = instructions[instruction].micro_operations[0].bus_program;
auto steps = all_micro_ops_[instructions[instruction].micro_operations].bus_program;
steps[0].microcycle.value = steps[1].microcycle.value = &program_counter_.halves.high;
steps[4].microcycle.value = steps[5].microcycle.value = &program_counter_.halves.low;
}

View File

@ -74,7 +74,7 @@ class ProcessorStorage {
HalfCycles half_cycles_left_to_run_;
HalfCycles e_clock_phase_;
enum class Operation {
enum class Operation: uint8_t {
None,
ABCD, SBCD, NBCD,
@ -335,21 +335,30 @@ class ProcessorStorage {
of micro-ops and, separately, the operation to perform plus whatever other
fields the operation requires.
TODO: this struct, as currently formed, is 48 bytes large on my 64-bit Intel
machine 8 bytes for each of the pointers, plus 8 bytes for the non-pointer fields.
That means that the Program[65536] table is 3mb large. Far too huge for a cache.
So slim this, even if it makes things much more painful to dereference.
Some of the fields are slightly convoluted in how they identify the information
they reference; this is done to keep this struct as small as possible due to
concerns about cache size.
(Aside: the compiler seems to prefer 8-byte alignment so eliminating or slimming the
non-pointer fields doesn't seem to be helpful immediately.)
On the 64-bit Intel processor this emulator was developed on, the struct below
adds up to 8 bytes; four for the initial uint32_t and then one each for the
remaining fields, with no additional padding being inserted by the compiler.
*/
struct Program {
/// The offset into the all_micro_ops_ at which micro-ops for this instruction begin.
uint32_t micro_operations = std::numeric_limits<uint32_t>::max();
/// The overarching operation applied by this program when the moment comes.
Operation operation;
/// The number of bytes after the beginning of an instance of ProcessorStorage that the RegisterPair32 containing
/// a source value for this operation lies at.
uint8_t source_offset = 0;
/// The number of bytes after the beginning of an instance of ProcessorStorage that the RegisterPair32 containing
/// a destination value for this operation lies at.
uint8_t destination_offset = 0;
/// A bitfield comprised of:
/// b7 = set if this program requires supervisor mode;
/// b0b2 = the source address register (for pre-decrement and post-increment actions); and
/// b4-b6 = destination address register.
uint8_t source_dest = 0;
bool requires_supervisor = false;
MicroOp *micro_operations = nullptr;
RegisterPair32 *source = nullptr;
RegisterPair32 *destination = nullptr;
void set_source_address(ProcessorStorage &storage, int index) {
source_dest = uint8_t((source_dest & 0x0f) | (index << 4));
@ -359,12 +368,18 @@ class ProcessorStorage {
source_dest = uint8_t((source_dest & 0xf0) | index);
}
void set_requires_supervisor(bool requires_supervisor) {
source_dest = (source_dest & 0x7f) | (requires_supervisor ? 0x80 : 0x00);
}
void set_source(ProcessorStorage &storage, RegisterPair32 *target) {
source = target;
source_offset = decltype(source_offset)(reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage));
assert(source_offset == (reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage)));
}
void set_destination(ProcessorStorage &storage, RegisterPair32 *target) {
destination = target;
destination_offset = decltype(destination_offset)(reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage));
assert(destination_offset == (reinterpret_cast<uint8_t *>(target) - reinterpret_cast<uint8_t *>(&storage)));
}
void set_source(ProcessorStorage &storage, int mode, int reg) {