From e6c4454059e0487de9c11a0324ded92e7b0d40ea Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 18 Apr 2024 22:13:58 -0400 Subject: [PATCH] Provide a means for SWI interception. --- InstructionSets/ARM/Executor.hpp | 91 ++++++++++++++---------- InstructionSets/ARM/OperationMapper.hpp | 17 ++++- Machines/Acorn/Archimedes/Archimedes.cpp | 4 ++ 3 files changed, 74 insertions(+), 38 deletions(-) diff --git a/InstructionSets/ARM/Executor.hpp b/InstructionSets/ARM/Executor.hpp index fd3ef5679..16c947d65 100644 --- a/InstructionSets/ARM/Executor.hpp +++ b/InstructionSets/ARM/Executor.hpp @@ -29,21 +29,28 @@ DestinationT read_bus(SourceT value) { } } -struct NullStatusHandler { +struct NullControlFlowHandler { + /// Indicates that a potential pipeline-affecting status flag change occurred, + /// i.e. a change to processor mode or interrupt flags. void did_set_status() {} + + /// Indicates that the PC was altered by the instruction. void did_set_pc() {} + + /// Provides notification that an SWI is about to happen along with the option of skipping it; this gives handlers the + /// chance to substitute a high-level reimplementation of the service call. + bool should_swi([[maybe_unused]] uint32_t comment) { return true; } }; /// A class compatible with the @c OperationMapper definition of a scheduler which applies all actions /// immediately, updating either a set of @c Registers or using the templated @c MemoryT to access /// memory. No hooks are currently provided for applying realistic timing. /// -/// If a StatusObserverT is specified, it'll receive calls to @c did_set_status() following every direct -/// write to the status bits — i.e. any change that can affect mode or interrupt flags. -template +/// If a ControlFlowHandlerT is specified, it'll receive calls as defined in the NullControlFlowHandler above. +template struct Executor { template - Executor(StatusObserverT &observer, Args &&...args) : bus(std::forward(args)...), status_observer_(observer) {} + Executor(ControlFlowHandlerT &handler, Args &&...args) : bus(std::forward(args)...), control_flow_handler_(handler) {} template Executor(Args &&...args) : bus(std::forward(args)...) {} @@ -195,8 +202,7 @@ struct Executor { } if(!is_comparison(flags.operation()) && fields.destination() == 15) { - registers_.set_pc(pc_proxy); - status_observer_.did_set_pc(); + set_pc(pc_proxy); } if constexpr (flags.set_condition_codes()) { // "When Rd is R15 and the S flag in the instruction is set, the PSR is overwritten by the @@ -204,8 +210,7 @@ struct Executor { // normally produce a result (CMP, CMN, TST, TEQ) ... the result will be used to update those // PSR flags which are not protected by virtue of the processor mode" if(fields.destination() == 15) { - registers_.set_status(conditions); - status_observer_.did_set_status(); + set_status(conditions); } else { // Set N and Z in a unified way. registers_.set_nz(conditions); @@ -251,8 +256,7 @@ struct Executor { if constexpr (flags.operation() == BranchFlags::Operation::BL) { registers_[14] = registers_.pc_status(0); } - registers_.set_pc(registers_.pc(4) + branch.offset()); - status_observer_.did_set_pc(); + set_pc(registers_.pc(4) + branch.offset()); } template void perform(SingleDataTransfer transfer) { @@ -291,7 +295,7 @@ struct Executor { // Check for an address exception. if(is_invalid_address(address)) { - registers_.exception(); + exception(); return; } @@ -325,7 +329,7 @@ struct Executor { } if(!did_write) { - registers_.exception(); + exception(); return; } } else { @@ -355,13 +359,12 @@ struct Executor { } if(!did_read) { - registers_.exception(); + exception(); return; } if(transfer.destination() == 15) { - registers_.set_pc(value); - status_observer_.did_set_pc(); + set_pc(value); } else { registers_[transfer.destination()] = value; } @@ -372,8 +375,7 @@ struct Executor { // So if this is a load, don't allow write back to overwrite what was loaded. if(flags.operation() == SingleDataTransferFlags::Operation::STR || transfer.base() != transfer.destination()) { if(transfer.base() == 15) { - registers_.set_pc(offsetted_address); - status_observer_.did_set_pc(); + set_pc(offsetted_address); } else { registers_[transfer.base()] = offsetted_address; } @@ -553,38 +555,38 @@ struct Executor { // Finally throw an exception if necessary. if(address_error) { - registers_.exception(); + exception(); } else if(!accesses_succeeded) { - registers_.exception(); + exception(); } else { // If this was an LDM to R15 then apply it appropriately. if(is_ldm && list & (1 << 15)) { - registers_.set_pc(pc_proxy); - status_observer_.did_set_pc(); + set_pc(pc_proxy); if constexpr (flags.load_psr()) { - registers_.set_status(pc_proxy); - status_observer_.did_set_status(); + set_status(pc_proxy); } } } } - void software_interrupt() { - registers_.exception(); + void software_interrupt(SoftwareInterrupt swi) { + if(control_flow_handler_.should_swi(swi.comment())) { + exception(); + } } void unknown() { - registers_.exception(); + exception(); } // Act as if no coprocessors present. template void perform(CoprocessorRegisterTransfer) { - registers_.exception(); + exception(); } template void perform(CoprocessorDataOperation) { - registers_.exception(); + exception(); } template void perform(CoprocessorDataTransfer) { - registers_.exception(); + exception(); } /// @returns The current registers state. @@ -600,13 +602,19 @@ struct Executor { /// Indicates a prefetch abort exception. void prefetch_abort() { - registers_.exception(); + exception(); } /// Sets the expected address of the instruction after whichever is about to be executed. /// So it's PC+4 compared to most other systems. + /// + /// By default this is not forwarded to the control-flow handler. + template void set_pc(uint32_t pc) { registers_.set_pc(pc); + if constexpr (notify) { + control_flow_handler_.did_set_pc(); + } } /// @returns The address of the instruction that should be fetched next. So as execution of each instruction @@ -619,12 +627,23 @@ struct Executor { MemoryT bus; private: - using StatusObserverTStorage = + template + void exception() { + registers_.exception(); + control_flow_handler_.did_set_pc(); + } + + void set_status(uint32_t status) { + registers_.set_status(status); + control_flow_handler_.did_set_status(); + } + + using ControlFlowHandlerTStorage = typename std::conditional< - std::is_same_v, - StatusObserverT, - StatusObserverT &>::type; - StatusObserverTStorage status_observer_; + std::is_same_v, + ControlFlowHandlerT, + ControlFlowHandlerT &>::type; + ControlFlowHandlerTStorage control_flow_handler_; Registers registers_; static bool is_invalid_address(uint32_t address) { diff --git a/InstructionSets/ARM/OperationMapper.hpp b/InstructionSets/ARM/OperationMapper.hpp index 08dbf411c..c136abbbe 100644 --- a/InstructionSets/ARM/OperationMapper.hpp +++ b/InstructionSets/ARM/OperationMapper.hpp @@ -388,6 +388,19 @@ private: uint8_t flags_; }; +// +// Software interrupt. +// +struct SoftwareInterrupt { + constexpr SoftwareInterrupt(uint32_t opcode) noexcept : opcode_(opcode) {} + + /// The 24-bit comment field, often decoded by the receiver of an SWI. + uint32_t comment() const { return opcode_ & 0xff'ffff; } + +private: + uint32_t opcode_; +}; + struct CoprocessorDataTransfer { constexpr CoprocessorDataTransfer(uint32_t opcode) noexcept : opcode_(opcode) {} @@ -461,7 +474,7 @@ struct OperationMapper { // Software interreupt; cf. p.35. if constexpr (((partial >> 24) & 0b1111) == 0b1111) { - scheduler.software_interrupt(); + scheduler.software_interrupt(SoftwareInterrupt(instruction)); return; } @@ -517,7 +530,7 @@ struct SampleScheduler { template void perform(CoprocessorDataTransfer); // Irregular operations. - void software_interrupt(); + void software_interrupt(SoftwareInterrupt); void unknown(); }; diff --git a/Machines/Acorn/Archimedes/Archimedes.cpp b/Machines/Acorn/Archimedes/Archimedes.cpp index bd0d0513c..0c33202a9 100644 --- a/Machines/Acorn/Archimedes/Archimedes.cpp +++ b/Machines/Acorn/Archimedes/Archimedes.cpp @@ -423,6 +423,10 @@ class ConcreteMachine: void did_set_pc() { } + bool should_swi(uint32_t) { + return true; + } + void update_clock_rates() { video_divider_ = executor_.bus.video().clock_divider(); }