From c630f86f33909d6e4db3914f04840a67104edc26 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 29 May 2023 22:56:36 +0100 Subject: [PATCH 01/13] Attempt to generalise out from the 9918's current sense of dispatching. --- ClockReceiver/Dispatcher.hpp | 89 +++++++++++++++++++ .../Clock Signal.xcodeproj/project.pbxproj | 4 +- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 ClockReceiver/Dispatcher.hpp diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp new file mode 100644 index 000000000..2773c6c55 --- /dev/null +++ b/ClockReceiver/Dispatcher.hpp @@ -0,0 +1,89 @@ +// +// Serialiser.hpp +// Clock Signal +// +// Created by Thomas Harte on 29/05/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#ifndef Dispatcher_hpp +#define Dispatcherr_hpp + +namespace Dispatcher { + +/// The unity function; converts n directly to n. +struct UnitConverter { + constexpr int operator ()(int n) { + return n; + } +}; + +template +struct Dispatcher { + + /// Perform @c target.perform() for the input range [start, end]; @c ConverterT()(n) will be applied to + /// each individual step before it becomes the relevant template argument. + void dispatch(SequencerT &target, int start, int end) { + + // Minor optimisation: do a comparison with end once outside the loop and if it implies so + // then do no further comparisons within the loop. This is somewhat targetted at expected + // use cases. + if(end < max) { + dispatch(target, start, end); + } else { + dispatch(target, start, end); + } + } + +private: + template void dispatch(SequencerT &target, int start, int end) { + static_assert(max < 2048); + + // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start + // and to allow a simple [[fallthrough]] through all subsequent steps up until end. + // So I don't think I have much in the way of options here. + // + // Sensible choices by the optimiser are assumed. + +#define index(n) \ + if constexpr (n == max) return; \ + if(use_end && end == n) return; \ + [[fallthrough]]; \ + case n: target.template perform(); + +#define index2(n) index(n); index(n+1); +#define index4(n) index2(n); index2(n+2); +#define index8(n) index4(n); index4(n+4); +#define index16(n) index8(n); index8(n+8); +#define index32(n) index16(n); index16(n+16); +#define index64(n) index32(n); index32(n+32); +#define index128(n) index64(n); index64(n+64); +#define index256(n) index128(n); index128(n+128); +#define index512(n) index256(n); index256(n+256); +#define index1024(n) index512(n); index512(n+512); +#define index2048(n) index1024(n); index1024(n+1024); + + switch(start) { + default: assert(false); + index2048(0); + } + +#undef index +#undef index2 +#undef index4 +#undef index8 +#undef index16 +#undef index32 +#undef index64 +#undef index128 +#undef index256 +#undef index512 +#undef index1024 +#undef index2048 + } + +}; + +} + +#endif /* Dispatcher_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 75e1fd830..953a4376f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1101,6 +1101,7 @@ /* Begin PBXFileReference section */ 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; + 428168382A254FBE008ECD27 /* Dispatcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Dispatcher.hpp; sourceTree = ""; }; 42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; 42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; 42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = ""; }; @@ -4967,12 +4968,13 @@ 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4B8A7E85212F988200F2BBC6 /* DeferredQueue.hpp */, + 4B99EBD026BF2D9F00CA924D /* DeferredValue.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4B80214322EE7C3E00068002 /* JustInTime.hpp */, 4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */, + 428168382A254FBE008ECD27 /* Dispatcher.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, 4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */, - 4B99EBD026BF2D9F00CA924D /* DeferredValue.hpp */, ); name = ClockReceiver; path = ../../ClockReceiver; From 4e12d5a70ac6e30b233ffcb35b0e43fbf1fa49cd Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 30 May 2023 16:43:22 +0100 Subject: [PATCH 02/13] Attempt to make switch sets even more obviously collapsible. --- ClockReceiver/Dispatcher.hpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp index 2773c6c55..e96ac97b7 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/Dispatcher.hpp @@ -21,8 +21,8 @@ struct UnitConverter { template struct Dispatcher { - /// Perform @c target.perform() for the input range [start, end]; @c ConverterT()(n) will be applied to - /// each individual step before it becomes the relevant template argument. + /// Perform @c target.perform() for the input range `start <= n < end`; + /// @c ConverterT()(n) will be applied to each individual step before it becomes the relevant template argument. void dispatch(SequencerT &target, int start, int end) { // Minor optimisation: do a comparison with end once outside the loop and if it implies so @@ -45,11 +45,16 @@ private: // // Sensible choices by the optimiser are assumed. -#define index(n) \ - if constexpr (n == max) return; \ - if(use_end && end == n) return; \ - [[fallthrough]]; \ - case n: target.template perform(); +#define index(n) \ + case n: \ + if constexpr (n <= max) { \ + if constexpr (n == max) return; \ + if constexpr (n < max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(); \ + } \ + [[fallthrough]]; #define index2(n) index(n); index(n+1); #define index4(n) index2(n); index2(n+2); From c0547f6e14dd21271126e29836b43a49bc4f4f1e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Jun 2023 15:58:13 -0400 Subject: [PATCH 03/13] Tidy up; forward construction arguments. --- ClockReceiver/Dispatcher.hpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp index e96ac97b7..299b87816 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/Dispatcher.hpp @@ -23,20 +23,21 @@ struct Dispatcher { /// Perform @c target.perform() for the input range `start <= n < end`; /// @c ConverterT()(n) will be applied to each individual step before it becomes the relevant template argument. - void dispatch(SequencerT &target, int start, int end) { + template + void dispatch(SequencerT &target, int start, int end, Args&&... args) { // Minor optimisation: do a comparison with end once outside the loop and if it implies so // then do no further comparisons within the loop. This is somewhat targetted at expected // use cases. if(end < max) { - dispatch(target, start, end); + dispatch(target, start, end, args...); } else { - dispatch(target, start, end); + dispatch(target, start, end, args...); } } private: - template void dispatch(SequencerT &target, int start, int end) { + template void dispatch(SequencerT &target, int start, int end, Args&&... args) { static_assert(max < 2048); // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start @@ -45,15 +46,15 @@ private: // // Sensible choices by the optimiser are assumed. -#define index(n) \ - case n: \ - if constexpr (n <= max) { \ - if constexpr (n == max) return; \ - if constexpr (n < max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(); \ - } \ +#define index(n) \ + case n: \ + if constexpr (n <= max) { \ + if constexpr (n == max) return; \ + if constexpr (n < max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(args...); \ + } \ [[fallthrough]]; #define index2(n) index(n); index(n+1); From 8a831b1409492f9c0339ddef94a7d72f7019b336 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 10 Jun 2023 15:58:30 -0400 Subject: [PATCH 04/13] Import sketch for a potential range dispatcher. --- ClockReceiver/Dispatcher.hpp | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp index 299b87816..e7046bce2 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/Dispatcher.hpp @@ -93,3 +93,46 @@ private: } #endif /* Dispatcher_hpp */ + +/* +template constexpr int lower_bound() { + if constexpr (!x || ClassifierT::template type() != ClassifierT::template type()) { + return x; + } else { + return lower_bound(); + } +} + +template +void range_dispatch(TargetT &destination, int start, int end) { +#define case(x) case x: \ + if constexpr (x+1 == ClassifierT::max || ClassifierT::template type() != ClassifierT::template type()) { \ + const auto range_begin = std::max(start, lower_bound()); \ + const auto range_end = std::min(end, x + 1); \ + \ + if(range_begin == lower_bound()) {\ + destination.template begin()>(range_begin); \ + }\ + destination.template perform()>(range_begin, range_end); \ + if(range_end == x+1) {\ + destination.template end()>(range_begin); \ + }\ + if(x+1 >= end) { \ + break; \ + } \ + } \ + [[fallthrough]]; + + switch(start) { + case(0) + case(1) + case(2) + case(3) + case(4) + case(5) + } + +#undef case +} + + */ From 05d2e78f809e5fea82660392f0e1a79306982010 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Jun 2023 15:34:44 -0400 Subject: [PATCH 05/13] Conversion can be a separate step. --- ClockReceiver/Dispatcher.hpp | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp index e7046bce2..460f2aa8a 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/Dispatcher.hpp @@ -11,18 +11,10 @@ namespace Dispatcher { -/// The unity function; converts n directly to n. -struct UnitConverter { - constexpr int operator ()(int n) { - return n; - } -}; - -template +template struct Dispatcher { - /// Perform @c target.perform() for the input range `start <= n < end`; - /// @c ConverterT()(n) will be applied to each individual step before it becomes the relevant template argument. + /// Perform @c target.perform() for the input range `start <= n < end`. template void dispatch(SequencerT &target, int start, int end, Args&&... args) { @@ -46,15 +38,15 @@ private: // // Sensible choices by the optimiser are assumed. -#define index(n) \ - case n: \ - if constexpr (n <= max) { \ - if constexpr (n == max) return; \ - if constexpr (n < max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(args...); \ - } \ +#define index(n) \ + case n: \ + if constexpr (n <= max) { \ + if constexpr (n == max) return; \ + if constexpr (n < max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(args...); \ + } \ [[fallthrough]]; #define index2(n) index(n); index(n+1); From 77c67ab59d8a71a589aaf2884d56bccd31fbafc2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Jun 2023 15:35:33 -0400 Subject: [PATCH 06/13] Build `max` into the sequencer. --- ClockReceiver/Dispatcher.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp index 460f2aa8a..98fec3cd4 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/Dispatcher.hpp @@ -11,7 +11,7 @@ namespace Dispatcher { -template +template struct Dispatcher { /// Perform @c target.perform() for the input range `start <= n < end`. @@ -21,7 +21,7 @@ struct Dispatcher { // Minor optimisation: do a comparison with end once outside the loop and if it implies so // then do no further comparisons within the loop. This is somewhat targetted at expected // use cases. - if(end < max) { + if(end < SequencerT::max) { dispatch(target, start, end, args...); } else { dispatch(target, start, end, args...); @@ -30,7 +30,7 @@ struct Dispatcher { private: template void dispatch(SequencerT &target, int start, int end, Args&&... args) { - static_assert(max < 2048); + static_assert(SequencerT::max < 2048); // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start // and to allow a simple [[fallthrough]] through all subsequent steps up until end. @@ -38,15 +38,15 @@ private: // // Sensible choices by the optimiser are assumed. -#define index(n) \ - case n: \ - if constexpr (n <= max) { \ - if constexpr (n == max) return; \ - if constexpr (n < max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(args...); \ - } \ +#define index(n) \ + case n: \ + if constexpr (n <= SequencerT::max) { \ + if constexpr (n == SequencerT::max) return; \ + if constexpr (n < SequencerT::max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(args...); \ + } \ [[fallthrough]]; #define index2(n) index(n); index(n+1); From 1aa953dd4da43ed26ee461c233b050333af0cc8a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Jun 2023 15:52:10 -0400 Subject: [PATCH 07/13] Consolidate RangeDispatcher under Dispatcher's umbrella. --- ClockReceiver/Dispatcher.hpp | 106 ++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/Dispatcher.hpp index 98fec3cd4..cf9dbd05a 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/Dispatcher.hpp @@ -11,6 +11,14 @@ namespace Dispatcher { +/// Provides glue for a run of calls like: +/// +/// SequencerT.perform<0>(...) +/// SequencerT.perform<1>(...) +/// SequencerT.perform<2>(...) +/// ...etc... +/// +/// Allowing the caller to execute any subrange of the calls. template struct Dispatcher { @@ -38,15 +46,15 @@ private: // // Sensible choices by the optimiser are assumed. -#define index(n) \ - case n: \ - if constexpr (n <= SequencerT::max) { \ - if constexpr (n == SequencerT::max) return; \ - if constexpr (n < SequencerT::max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(args...); \ - } \ +#define index(n) \ + case n: \ + if constexpr (n <= SequencerT::max) { \ + if constexpr (n == SequencerT::max) return; \ + if constexpr (n < SequencerT::max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(start, end, args...); \ + } \ [[fallthrough]]; #define index2(n) index(n); index(n+1); @@ -82,49 +90,43 @@ private: }; +/// An optional target for a Dispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target +/// only to begin and end each subrange, and for the number of cycles spent within. +template +struct RangeDispatcher { + static constexpr int max = ClassifierT::max; + + template + void perform(int begin, int end, Arg&&... args) { + constexpr auto region = ClassifierT::region(n); + const auto clipped_start = std::max(start, find_begin(n)); + const auto clipped_end = std::min(end, find_end(n)); + + if constexpr (n == find_begin(n)) { + target.begin(clipped_start); + } + + target.advance(clipped_end - clipped_start); + + if constexpr (n + 1 == find_end(n)) { + target.end(clipped_end); + } + } + + private: + constexpr int find_begin(int n) { + const auto type = ClassifierT::region(n); + while(n && ClassifierT::region(n - 1) == type) --n; + return n; + } + + constexpr int find_end(int n) { + const auto type = ClassifierT::region(n); + while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; + return n; + } +}; + } #endif /* Dispatcher_hpp */ - -/* -template constexpr int lower_bound() { - if constexpr (!x || ClassifierT::template type() != ClassifierT::template type()) { - return x; - } else { - return lower_bound(); - } -} - -template -void range_dispatch(TargetT &destination, int start, int end) { -#define case(x) case x: \ - if constexpr (x+1 == ClassifierT::max || ClassifierT::template type() != ClassifierT::template type()) { \ - const auto range_begin = std::max(start, lower_bound()); \ - const auto range_end = std::min(end, x + 1); \ - \ - if(range_begin == lower_bound()) {\ - destination.template begin()>(range_begin); \ - }\ - destination.template perform()>(range_begin, range_end); \ - if(range_end == x+1) {\ - destination.template end()>(range_begin); \ - }\ - if(x+1 >= end) { \ - break; \ - } \ - } \ - [[fallthrough]]; - - switch(start) { - case(0) - case(1) - case(2) - case(3) - case(4) - case(5) - } - -#undef case -} - - */ From d0285553612aa379647205ee1014dc9ede17c6d1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Jun 2023 16:09:02 -0400 Subject: [PATCH 08/13] Get code up on feet, fix most obvious transgressions. --- .../{Dispatcher.hpp => RangeDispatcher.hpp} | 32 ++++++----- .../Clock Signal.xcodeproj/project.pbxproj | 8 ++- .../Mac/Clock SignalTests/DispatcherTests.mm | 54 +++++++++++++++++++ 3 files changed, 78 insertions(+), 16 deletions(-) rename ClockReceiver/{Dispatcher.hpp => RangeDispatcher.hpp} (81%) create mode 100644 OSBindings/Mac/Clock SignalTests/DispatcherTests.mm diff --git a/ClockReceiver/Dispatcher.hpp b/ClockReceiver/RangeDispatcher.hpp similarity index 81% rename from ClockReceiver/Dispatcher.hpp rename to ClockReceiver/RangeDispatcher.hpp index cf9dbd05a..24cd3e186 100644 --- a/ClockReceiver/Dispatcher.hpp +++ b/ClockReceiver/RangeDispatcher.hpp @@ -1,15 +1,17 @@ // -// Serialiser.hpp +// RangeDispatcher.hpp // Clock Signal // // Created by Thomas Harte on 29/05/2023. // Copyright © 2023 Thomas Harte. All rights reserved. // -#ifndef Dispatcher_hpp -#define Dispatcherr_hpp +#ifndef RangeDispatcher_hpp +#define RangeDispatcher_hpp -namespace Dispatcher { +#include + +namespace Reflection { /// Provides glue for a run of calls like: /// @@ -20,11 +22,11 @@ namespace Dispatcher { /// /// Allowing the caller to execute any subrange of the calls. template -struct Dispatcher { +struct RangeDispatcher { /// Perform @c target.perform() for the input range `start <= n < end`. template - void dispatch(SequencerT &target, int start, int end, Args&&... args) { + static void dispatch(SequencerT &target, int start, int end, Args&&... args) { // Minor optimisation: do a comparison with end once outside the loop and if it implies so // then do no further comparisons within the loop. This is somewhat targetted at expected @@ -37,7 +39,7 @@ struct Dispatcher { } private: - template void dispatch(SequencerT &target, int start, int end, Args&&... args) { + template static void dispatch(SequencerT &target, int start, int end, Args&&... args) { static_assert(SequencerT::max < 2048); // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start @@ -90,16 +92,16 @@ private: }; -/// An optional target for a Dispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target +/// An optional target for a RangeDispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target /// only to begin and end each subrange, and for the number of cycles spent within. -template -struct RangeDispatcher { +template +struct SubrangeDispatcher { static constexpr int max = ClassifierT::max; - template - void perform(int begin, int end, Arg&&... args) { + template + void perform(int begin, int end) { constexpr auto region = ClassifierT::region(n); - const auto clipped_start = std::max(start, find_begin(n)); + const auto clipped_start = std::max(begin, find_begin(n)); const auto clipped_end = std::min(end, find_end(n)); if constexpr (n == find_begin(n)) { @@ -125,8 +127,10 @@ struct RangeDispatcher { while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; return n; } + + TargetT ⌖ }; } -#endif /* Dispatcher_hpp */ +#endif /* RangeDispatcher_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 953a4376f..a7cbf8743 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 4281683A2A37AFB4008ECD27 /* DispatcherTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 428168392A37AFB4008ECD27 /* DispatcherTests.mm */; }; 4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; }; 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; }; 4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; }; @@ -1101,7 +1102,8 @@ /* Begin PBXFileReference section */ 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; - 428168382A254FBE008ECD27 /* Dispatcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Dispatcher.hpp; sourceTree = ""; }; + 428168382A254FBE008ECD27 /* RangeDispatcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RangeDispatcher.hpp; sourceTree = ""; }; + 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; 42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; 42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; 42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = ""; }; @@ -4287,6 +4289,7 @@ 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */, 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, 4BB0CAA627E51B6300672A88 /* DingusdevPowerPCTests.mm */, + 428168392A37AFB4008ECD27 /* DispatcherTests.mm */, 4BFF1D3C2235C3C100838EA1 /* EmuTOSTests.mm */, 4B47770C26900685005C2340 /* EnterpriseDaveTests.mm */, 4B051CB2267D3FF800CA44E8 /* EnterpriseNickTests.mm */, @@ -4972,7 +4975,7 @@ 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4B80214322EE7C3E00068002 /* JustInTime.hpp */, 4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */, - 428168382A254FBE008ECD27 /* Dispatcher.hpp */, + 428168382A254FBE008ECD27 /* RangeDispatcher.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, 4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */, ); @@ -6155,6 +6158,7 @@ 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */, 4B778F3823A5F11C0000D260 /* SegmentParser.cpp in Sources */, 4B778F0723A5EC150000D260 /* CommodoreTAP.cpp in Sources */, + 4281683A2A37AFB4008ECD27 /* DispatcherTests.mm in Sources */, 4B778F4123A5F19A0000D260 /* MemoryPacker.cpp in Sources */, 4B778F4423A5F1BE0000D260 /* CommodoreGCR.cpp in Sources */, 4B778EF923A5EB740000D260 /* MSA.cpp in Sources */, diff --git a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm new file mode 100644 index 000000000..25cb0ee95 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm @@ -0,0 +1,54 @@ +// +// DispatcherTests.m +// Clock SignalTests +// +// Created by Thomas Harte on 12/06/2023. +// Copyright © 2023 Thomas Harte. All rights reserved. +// + +#import + +#include "RangeDispatcher.hpp" + +#include +#include + +@interface DispatcherTests : XCTestCase +@end + +@implementation DispatcherTests { +} + +- (void)setUp { +} + +- (void)tearDown { +} + +struct DoStep { + static constexpr int max = 100; + template void perform(int, int) { + assert(n < max); + performed[n] = true; + } + std::array performed{}; +}; + +- (void)testPoints { + DoStep stepper; + + Reflection::RangeDispatcher::dispatch(stepper, 0, 10); + for(size_t c = 0; c < stepper.performed.size(); c++) { + XCTAssert(stepper.performed[c] == (c < 10)); + } + + Reflection::RangeDispatcher::dispatch(stepper, 29, 100000); + for(size_t c = 0; c < stepper.performed.size(); c++) { + XCTAssert(stepper.performed[c] == (c < 10) || (c >= 29)); + } +} + +- (void)testRanges { +} + +@end From b00eac4a34c50b7e40a445dac22b7e7c9311d061 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 12 Jun 2023 23:16:45 -0400 Subject: [PATCH 09/13] Get to building. --- ClockReceiver/RangeDispatcher.hpp | 15 ++++--- .../Mac/Clock SignalTests/DispatcherTests.mm | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/ClockReceiver/RangeDispatcher.hpp b/ClockReceiver/RangeDispatcher.hpp index 24cd3e186..a327b8e67 100644 --- a/ClockReceiver/RangeDispatcher.hpp +++ b/ClockReceiver/RangeDispatcher.hpp @@ -98,6 +98,9 @@ template struct SubrangeDispatcher { static constexpr int max = ClassifierT::max; + template SubrangeDispatcher(Args&&... args) : + target(std::forward(args)...) {} + template void perform(int begin, int end) { constexpr auto region = ClassifierT::region(n); @@ -105,30 +108,30 @@ struct SubrangeDispatcher { const auto clipped_end = std::min(end, find_end(n)); if constexpr (n == find_begin(n)) { - target.begin(clipped_start); + target.template begin(clipped_start); } - target.advance(clipped_end - clipped_start); + target.template advance(clipped_end - clipped_start); if constexpr (n + 1 == find_end(n)) { - target.end(clipped_end); + target.template end(clipped_end); } } private: - constexpr int find_begin(int n) { + static constexpr int find_begin(int n) { const auto type = ClassifierT::region(n); while(n && ClassifierT::region(n - 1) == type) --n; return n; } - constexpr int find_end(int n) { + static constexpr int find_end(int n) { const auto type = ClassifierT::region(n); while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; return n; } - TargetT ⌖ + TargetT target; }; } diff --git a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm index 25cb0ee95..f5545ac75 100644 --- a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm +++ b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm @@ -12,6 +12,7 @@ #include #include +#include @interface DispatcherTests : XCTestCase @end @@ -48,7 +49,49 @@ struct DoStep { } } +enum class RangeType { + Sync, Border +}; + +struct RangeClassifier { + static constexpr int max = 50; + + static constexpr RangeType region(int x) { + return x >= 10 && x < 20 ? RangeType::Sync : RangeType::Border; + } +}; + +struct RangeTarget { + struct Event { + enum class Type { + Begin, End, Advance + }; + Type event_type; + RangeType range_type; + int length = 0; + + Event(Type event_type, RangeType range_type) : event_type(event_type), range_type(range_type) {} + Event(Type event_type, RangeType range_type, int length) : event_type(event_type), range_type(range_type), length(length) {} + }; + std::vector events; + + template void begin(int) { + events.emplace_back(Event::Type::Begin, type); + } + template void end(int) { + events.emplace_back(Event::Type::End, type); + } + template void advance(int length) { + events.emplace_back(Event::Type::Advance, type, length); + } +}; + - (void)testRanges { + using Dispatcher = Reflection::SubrangeDispatcher; + Dispatcher dispatcher; + Reflection::RangeDispatcher::dispatch(dispatcher, 0, 10); + + printf(""); } @end From 6261ac24b4e401816c09039dd5d9c652a3e40822 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Jun 2023 12:46:21 -0400 Subject: [PATCH 10/13] Reformat SubrangeDispatcher; test. --- ClockReceiver/RangeDispatcher.hpp | 196 +++++++++--------- .../Mac/Clock SignalTests/DispatcherTests.mm | 43 +++- 2 files changed, 135 insertions(+), 104 deletions(-) diff --git a/ClockReceiver/RangeDispatcher.hpp b/ClockReceiver/RangeDispatcher.hpp index a327b8e67..e3c81e067 100644 --- a/ClockReceiver/RangeDispatcher.hpp +++ b/ClockReceiver/RangeDispatcher.hpp @@ -13,52 +13,11 @@ namespace Reflection { -/// Provides glue for a run of calls like: -/// -/// SequencerT.perform<0>(...) -/// SequencerT.perform<1>(...) -/// SequencerT.perform<2>(...) -/// ...etc... -/// -/// Allowing the caller to execute any subrange of the calls. -template -struct RangeDispatcher { - - /// Perform @c target.perform() for the input range `start <= n < end`. - template - static void dispatch(SequencerT &target, int start, int end, Args&&... args) { - - // Minor optimisation: do a comparison with end once outside the loop and if it implies so - // then do no further comparisons within the loop. This is somewhat targetted at expected - // use cases. - if(end < SequencerT::max) { - dispatch(target, start, end, args...); - } else { - dispatch(target, start, end, args...); - } - } - -private: - template static void dispatch(SequencerT &target, int start, int end, Args&&... args) { - static_assert(SequencerT::max < 2048); - - // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start - // and to allow a simple [[fallthrough]] through all subsequent steps up until end. - // So I don't think I have much in the way of options here. - // - // Sensible choices by the optimiser are assumed. - -#define index(n) \ - case n: \ - if constexpr (n <= SequencerT::max) { \ - if constexpr (n == SequencerT::max) return; \ - if constexpr (n < SequencerT::max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(start, end, args...); \ - } \ - [[fallthrough]]; - +// Yes, macros, yuck. But I want an actual switch statement for the dispatch to start +// and to allow a simple [[fallthrough]] through all subsequent steps up until end. +// So I don't think I have much in the way of options here. +// +// Sensible choices by the optimiser are assumed. #define index2(n) index(n); index(n+1); #define index4(n) index2(n); index2(n+2); #define index8(n) index4(n); index4(n+4); @@ -71,12 +30,106 @@ private: #define index1024(n) index512(n); index512(n+512); #define index2048(n) index1024(n); index1024(n+1024); - switch(start) { - default: assert(false); - index2048(0); +/// Provides glue for a run of calls like: +/// +/// SequencerT.perform<0>(...) +/// SequencerT.perform<1>(...) +/// SequencerT.perform<2>(...) +/// ...etc... +/// +/// Allowing the caller to execute any subrange of the calls. +template +struct RangeDispatcher { + static_assert(SequencerT::max < 2048); + + /// Perform @c target.perform() for the input range `begin <= n < end`. + template + static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { + + // Minor optimisation: do a comparison with end once outside the loop and if it implies so + // then do no further comparisons within the loop. This is somewhat targetted at expected + // use cases. + if(end < SequencerT::max) { + dispatch(target, begin, end, args...); + } else { + dispatch(target, begin, end, args...); } + } + + private: + template static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { +#define index(n) \ + case n: \ + if constexpr (n <= SequencerT::max) { \ + if constexpr (n == SequencerT::max) return; \ + if constexpr (n < SequencerT::max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(args...); \ + } \ + [[fallthrough]]; + + switch(begin) { + default: assert(false); + index2048(0); + } #undef index + } + +}; + +/// An optional target for a RangeDispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target +/// only to begin and end each subrange, and for the number of cycles spent within. +template +struct SubrangeDispatcher { + static_assert(ClassifierT::max < 2048); + + template static void dispatch(TargetT &target, int begin, int end, Args&&... args) { +#define index(n) \ + case n: \ + if constexpr (n <= ClassifierT::max) { \ + constexpr auto region = ClassifierT::region(n); \ + if constexpr (n == find_begin(n)) { \ + if(n >= end) { \ + return; \ + } \ + target.template begin(n); \ + } \ + if constexpr (n == find_end(n) - 1) { \ + const auto clipped_begin = std::max(begin, find_begin(n)); \ + const auto clipped_end = std::min(end, find_end(n)); \ + target.template advance(clipped_end - clipped_begin); \ + \ + if(clipped_end == n + 1) { \ + target.template end(n + 1); \ + } \ + } \ + } \ + [[fallthrough]]; + + switch(begin) { + default: assert(false); + index2048(0); + } + +#undef index + } + + private: + static constexpr int find_begin(int n) { + const auto type = ClassifierT::region(n); + while(n && ClassifierT::region(n - 1) == type) --n; + return n; + } + + static constexpr int find_end(int n) { + const auto type = ClassifierT::region(n); + while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; + return n; + } +}; + #undef index2 #undef index4 #undef index8 @@ -88,51 +141,6 @@ private: #undef index512 #undef index1024 #undef index2048 - } - -}; - -/// An optional target for a RangeDispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target -/// only to begin and end each subrange, and for the number of cycles spent within. -template -struct SubrangeDispatcher { - static constexpr int max = ClassifierT::max; - - template SubrangeDispatcher(Args&&... args) : - target(std::forward(args)...) {} - - template - void perform(int begin, int end) { - constexpr auto region = ClassifierT::region(n); - const auto clipped_start = std::max(begin, find_begin(n)); - const auto clipped_end = std::min(end, find_end(n)); - - if constexpr (n == find_begin(n)) { - target.template begin(clipped_start); - } - - target.template advance(clipped_end - clipped_start); - - if constexpr (n + 1 == find_end(n)) { - target.template end(clipped_end); - } - } - - private: - static constexpr int find_begin(int n) { - const auto type = ClassifierT::region(n); - while(n && ClassifierT::region(n - 1) == type) --n; - return n; - } - - static constexpr int find_end(int n) { - const auto type = ClassifierT::region(n); - while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; - return n; - } - - TargetT target; -}; } diff --git a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm index f5545ac75..af22e1ab0 100644 --- a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm +++ b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm @@ -28,7 +28,7 @@ struct DoStep { static constexpr int max = 100; - template void perform(int, int) { + template void perform() { assert(n < max); performed[n] = true; } @@ -54,7 +54,7 @@ enum class RangeType { }; struct RangeClassifier { - static constexpr int max = 50; + static constexpr int max = 200; static constexpr RangeType region(int x) { return x >= 10 && x < 20 ? RangeType::Sync : RangeType::Border; @@ -70,16 +70,21 @@ struct RangeTarget { RangeType range_type; int length = 0; - Event(Type event_type, RangeType range_type) : event_type(event_type), range_type(range_type) {} Event(Type event_type, RangeType range_type, int length) : event_type(event_type), range_type(range_type), length(length) {} + + bool operator ==(const Event &rhs) const { + if(rhs.event_type != event_type) return false; + if(rhs.range_type != range_type) return false; + return rhs.length == length; + } }; std::vector events; - template void begin(int) { - events.emplace_back(Event::Type::Begin, type); + template void begin(int position) { + events.emplace_back(Event::Type::Begin, type, position); } - template void end(int) { - events.emplace_back(Event::Type::End, type); + template void end(int position) { + events.emplace_back(Event::Type::End, type, position); } template void advance(int length) { events.emplace_back(Event::Type::Advance, type, length); @@ -87,11 +92,29 @@ struct RangeTarget { }; - (void)testRanges { + RangeTarget target; using Dispatcher = Reflection::SubrangeDispatcher; - Dispatcher dispatcher; - Reflection::RangeDispatcher::dispatch(dispatcher, 0, 10); + Dispatcher::dispatch(target, 0, 11); - printf(""); + XCTAssertEqual(target.events.size(), 5); + XCTAssert(target.events[0] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Border, 0)); + XCTAssert(target.events[1] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 10)); + XCTAssert(target.events[2] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 10)); + XCTAssert(target.events[3] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Sync, 10)); + XCTAssert(target.events[4] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Sync, 1)); + + Dispatcher::dispatch(target, 11, 75); + Dispatcher::dispatch(target, 75, 100); + Dispatcher::dispatch(target, 100, 200); + + XCTAssertEqual(target.events.size(), 12); + XCTAssert(target.events[5] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Sync, 9)); + XCTAssert(target.events[6] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Sync, 20)); + XCTAssert(target.events[7] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Border, 20)); + XCTAssert(target.events[8] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 55)); + XCTAssert(target.events[9] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 25)); + XCTAssert(target.events[10] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 100)); + XCTAssert(target.events[11] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 200)); } @end From de5ee8f0d0c01976b67c9d1173de60e5c6455488 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Jun 2023 13:26:39 -0400 Subject: [PATCH 11/13] Mildly extend test. --- OSBindings/Mac/Clock SignalTests/DispatcherTests.mm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm index af22e1ab0..ddf21b2be 100644 --- a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm +++ b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm @@ -105,16 +105,19 @@ struct RangeTarget { Dispatcher::dispatch(target, 11, 75); Dispatcher::dispatch(target, 75, 100); - Dispatcher::dispatch(target, 100, 200); + Dispatcher::dispatch(target, 100, 199); + Dispatcher::dispatch(target, 199, 200); + Dispatcher::dispatch(target, 200, 400); // Out of range. - XCTAssertEqual(target.events.size(), 12); + XCTAssertEqual(target.events.size(), 13); XCTAssert(target.events[5] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Sync, 9)); XCTAssert(target.events[6] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Sync, 20)); XCTAssert(target.events[7] == RangeTarget::Event(RangeTarget::Event::Type::Begin, RangeType::Border, 20)); XCTAssert(target.events[8] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 55)); XCTAssert(target.events[9] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 25)); - XCTAssert(target.events[10] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 100)); - XCTAssert(target.events[11] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 200)); + XCTAssert(target.events[10] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 99)); + XCTAssert(target.events[11] == RangeTarget::Event(RangeTarget::Event::Type::Advance, RangeType::Border, 1)); + XCTAssert(target.events[12] == RangeTarget::Event(RangeTarget::Event::Type::End, RangeType::Border, 200)); } @end From d36a88dd11a0f64cfb0a404520d8cd5a86e39a60 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Jun 2023 15:22:53 -0400 Subject: [PATCH 12/13] Collect up different dispatches. --- ClockReceiver/RangeDispatcher.hpp | 53 ++++++++++++++----- .../6809OperationMapperTests.mm | 2 +- Reflection/Dispatcher.hpp | 19 ------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/ClockReceiver/RangeDispatcher.hpp b/ClockReceiver/RangeDispatcher.hpp index e3c81e067..c0827a37c 100644 --- a/ClockReceiver/RangeDispatcher.hpp +++ b/ClockReceiver/RangeDispatcher.hpp @@ -10,9 +10,31 @@ #define RangeDispatcher_hpp #include +#include namespace Reflection { +/*! + Calls @c t.dispatch(args...) as efficiently as possible. +*/ +template void dispatch(TargetT &t, uint8_t c, Args &&... args) { +#define Opt(x) case x: t.template dispatch(std::forward(args)...); break +#define Opt4(x) Opt(x + 0x00); Opt(x + 0x01); Opt(x + 0x02); Opt(x + 0x03) +#define Opt16(x) Opt4(x + 0x00); Opt4(x + 0x04); Opt4(x + 0x08); Opt4(x + 0x0c) +#define Opt64(x) Opt16(x + 0x00); Opt16(x + 0x10); Opt16(x + 0x20); Opt16(x + 0x30) +#define Opt256(x) Opt64(x + 0x00); Opt64(x + 0x40); Opt64(x + 0x80); Opt64(x + 0xc0) + + switch(c) { + Opt256(0); + } + +#undef Opt256 +#undef Opt64 +#undef Opt16 +#undef Opt4 +#undef Opt +} + // Yes, macros, yuck. But I want an actual switch statement for the dispatch to start // and to allow a simple [[fallthrough]] through all subsequent steps up until end. // So I don't think I have much in the way of options here. @@ -30,6 +52,9 @@ namespace Reflection { #define index1024(n) index512(n); index512(n+512); #define index2048(n) index1024(n); index1024(n+1024); +#define switch_indices(x) switch(x) { default: assert(false); index2048(0); } +static constexpr int switch_max = 2048; + /// Provides glue for a run of calls like: /// /// SequencerT.perform<0>(...) @@ -40,7 +65,7 @@ namespace Reflection { /// Allowing the caller to execute any subrange of the calls. template struct RangeDispatcher { - static_assert(SequencerT::max < 2048); + static_assert(SequencerT::max < switch_max); /// Perform @c target.perform() for the input range `begin <= n < end`. template @@ -69,23 +94,26 @@ struct RangeDispatcher { } \ [[fallthrough]]; - switch(begin) { - default: assert(false); - index2048(0); - } + switch_indices(begin); #undef index } }; -/// An optional target for a RangeDispatcher which uses a classifier to divide the input region into typed ranges, issuing calls to the target -/// only to begin and end each subrange, and for the number of cycles spent within. +/// Uses a classifier to divide a range into typed subranges and issues calls to a target of the form: +/// +/// * begin(location) upon entering a new region; +/// * end(location) upon entering a region; and +/// * advance(distance) in as many chunks as required to populate the inside of the region. +/// +/// @c begin and @c end have iterator-style semantics: begin's location will be the first location in the relevant subrange and +/// end's will be the first location not in the relevant subrange. template struct SubrangeDispatcher { - static_assert(ClassifierT::max < 2048); + static_assert(ClassifierT::max < switch_max); - template static void dispatch(TargetT &target, int begin, int end, Args&&... args) { + static void dispatch(TargetT &target, int begin, int end) { #define index(n) \ case n: \ if constexpr (n <= ClassifierT::max) { \ @@ -108,10 +136,7 @@ struct SubrangeDispatcher { } \ [[fallthrough]]; - switch(begin) { - default: assert(false); - index2048(0); - } + switch_indices(begin); #undef index } @@ -130,6 +155,8 @@ struct SubrangeDispatcher { } }; +#undef switch_indices + #undef index2 #undef index4 #undef index8 diff --git a/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm b/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm index f8757a8c9..fd273bad7 100644 --- a/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm +++ b/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm @@ -9,7 +9,7 @@ #import -#include "Dispatcher.hpp" +#include "RangeDispatcher.hpp" #include "../../../InstructionSets/6809/OperationMapper.hpp" using namespace InstructionSet::M6809; diff --git a/Reflection/Dispatcher.hpp b/Reflection/Dispatcher.hpp index adfb025f6..71829bc56 100644 --- a/Reflection/Dispatcher.hpp +++ b/Reflection/Dispatcher.hpp @@ -13,26 +13,7 @@ namespace Reflection { -/*! - Calls @c t.dispatch(args...) as efficiently as possible. -*/ -template void dispatch(TargetT &t, uint8_t c, Args &&... args) { -#define Opt(x) case x: t.template dispatch(std::forward(args)...); break -#define Opt4(x) Opt(x + 0x00); Opt(x + 0x01); Opt(x + 0x02); Opt(x + 0x03) -#define Opt16(x) Opt4(x + 0x00); Opt4(x + 0x04); Opt4(x + 0x08); Opt4(x + 0x0c) -#define Opt64(x) Opt16(x + 0x00); Opt16(x + 0x10); Opt16(x + 0x20); Opt16(x + 0x30) -#define Opt256(x) Opt64(x + 0x00); Opt64(x + 0x40); Opt64(x + 0x80); Opt64(x + 0xc0) - switch(c) { - Opt256(0); - } - -#undef Opt256 -#undef Opt64 -#undef Opt16 -#undef Opt4 -#undef Opt -} } From 1d8bc4172424f692e80c66e9b3c612ebeb41f769 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 13 Jun 2023 15:25:51 -0400 Subject: [PATCH 13/13] Shift back to original name. --- ClockReceiver/RangeDispatcher.hpp | 174 ------------------ .../Clock Signal.xcodeproj/project.pbxproj | 8 +- .../6809OperationMapperTests.mm | 2 +- .../Mac/Clock SignalTests/DispatcherTests.mm | 11 +- Reflection/Dispatcher.hpp | 154 ++++++++++++++++ 5 files changed, 160 insertions(+), 189 deletions(-) delete mode 100644 ClockReceiver/RangeDispatcher.hpp diff --git a/ClockReceiver/RangeDispatcher.hpp b/ClockReceiver/RangeDispatcher.hpp deleted file mode 100644 index c0827a37c..000000000 --- a/ClockReceiver/RangeDispatcher.hpp +++ /dev/null @@ -1,174 +0,0 @@ -// -// RangeDispatcher.hpp -// Clock Signal -// -// Created by Thomas Harte on 29/05/2023. -// Copyright © 2023 Thomas Harte. All rights reserved. -// - -#ifndef RangeDispatcher_hpp -#define RangeDispatcher_hpp - -#include -#include - -namespace Reflection { - -/*! - Calls @c t.dispatch(args...) as efficiently as possible. -*/ -template void dispatch(TargetT &t, uint8_t c, Args &&... args) { -#define Opt(x) case x: t.template dispatch(std::forward(args)...); break -#define Opt4(x) Opt(x + 0x00); Opt(x + 0x01); Opt(x + 0x02); Opt(x + 0x03) -#define Opt16(x) Opt4(x + 0x00); Opt4(x + 0x04); Opt4(x + 0x08); Opt4(x + 0x0c) -#define Opt64(x) Opt16(x + 0x00); Opt16(x + 0x10); Opt16(x + 0x20); Opt16(x + 0x30) -#define Opt256(x) Opt64(x + 0x00); Opt64(x + 0x40); Opt64(x + 0x80); Opt64(x + 0xc0) - - switch(c) { - Opt256(0); - } - -#undef Opt256 -#undef Opt64 -#undef Opt16 -#undef Opt4 -#undef Opt -} - -// Yes, macros, yuck. But I want an actual switch statement for the dispatch to start -// and to allow a simple [[fallthrough]] through all subsequent steps up until end. -// So I don't think I have much in the way of options here. -// -// Sensible choices by the optimiser are assumed. -#define index2(n) index(n); index(n+1); -#define index4(n) index2(n); index2(n+2); -#define index8(n) index4(n); index4(n+4); -#define index16(n) index8(n); index8(n+8); -#define index32(n) index16(n); index16(n+16); -#define index64(n) index32(n); index32(n+32); -#define index128(n) index64(n); index64(n+64); -#define index256(n) index128(n); index128(n+128); -#define index512(n) index256(n); index256(n+256); -#define index1024(n) index512(n); index512(n+512); -#define index2048(n) index1024(n); index1024(n+1024); - -#define switch_indices(x) switch(x) { default: assert(false); index2048(0); } -static constexpr int switch_max = 2048; - -/// Provides glue for a run of calls like: -/// -/// SequencerT.perform<0>(...) -/// SequencerT.perform<1>(...) -/// SequencerT.perform<2>(...) -/// ...etc... -/// -/// Allowing the caller to execute any subrange of the calls. -template -struct RangeDispatcher { - static_assert(SequencerT::max < switch_max); - - /// Perform @c target.perform() for the input range `begin <= n < end`. - template - static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { - - // Minor optimisation: do a comparison with end once outside the loop and if it implies so - // then do no further comparisons within the loop. This is somewhat targetted at expected - // use cases. - if(end < SequencerT::max) { - dispatch(target, begin, end, args...); - } else { - dispatch(target, begin, end, args...); - } - } - - private: - template static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { -#define index(n) \ - case n: \ - if constexpr (n <= SequencerT::max) { \ - if constexpr (n == SequencerT::max) return; \ - if constexpr (n < SequencerT::max) { \ - if(use_end && end == n) return; \ - } \ - target.template perform(args...); \ - } \ - [[fallthrough]]; - - switch_indices(begin); - -#undef index - } - -}; - -/// Uses a classifier to divide a range into typed subranges and issues calls to a target of the form: -/// -/// * begin(location) upon entering a new region; -/// * end(location) upon entering a region; and -/// * advance(distance) in as many chunks as required to populate the inside of the region. -/// -/// @c begin and @c end have iterator-style semantics: begin's location will be the first location in the relevant subrange and -/// end's will be the first location not in the relevant subrange. -template -struct SubrangeDispatcher { - static_assert(ClassifierT::max < switch_max); - - static void dispatch(TargetT &target, int begin, int end) { -#define index(n) \ - case n: \ - if constexpr (n <= ClassifierT::max) { \ - constexpr auto region = ClassifierT::region(n); \ - if constexpr (n == find_begin(n)) { \ - if(n >= end) { \ - return; \ - } \ - target.template begin(n); \ - } \ - if constexpr (n == find_end(n) - 1) { \ - const auto clipped_begin = std::max(begin, find_begin(n)); \ - const auto clipped_end = std::min(end, find_end(n)); \ - target.template advance(clipped_end - clipped_begin); \ - \ - if(clipped_end == n + 1) { \ - target.template end(n + 1); \ - } \ - } \ - } \ - [[fallthrough]]; - - switch_indices(begin); - -#undef index - } - - private: - static constexpr int find_begin(int n) { - const auto type = ClassifierT::region(n); - while(n && ClassifierT::region(n - 1) == type) --n; - return n; - } - - static constexpr int find_end(int n) { - const auto type = ClassifierT::region(n); - while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; - return n; - } -}; - -#undef switch_indices - -#undef index2 -#undef index4 -#undef index8 -#undef index16 -#undef index32 -#undef index64 -#undef index128 -#undef index256 -#undef index512 -#undef index1024 -#undef index2048 - -} - -#endif /* RangeDispatcher_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a7cbf8743..2803a2130 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1102,7 +1102,6 @@ /* Begin PBXFileReference section */ 428168372A16C25C008ECD27 /* LineLayout.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LineLayout.hpp; sourceTree = ""; }; - 428168382A254FBE008ECD27 /* RangeDispatcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RangeDispatcher.hpp; sourceTree = ""; }; 428168392A37AFB4008ECD27 /* DispatcherTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DispatcherTests.mm; sourceTree = ""; }; 42AD552E2A0C4D5000ACE410 /* 68000.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000.hpp; sourceTree = ""; }; 42AD55302A0C4D5000ACE410 /* 68000Storage.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Storage.hpp; sourceTree = ""; }; @@ -2783,10 +2782,10 @@ 4B3AF7CF2413470E00873C0B /* Reflection */ = { isa = PBXGroup; children = ( - 4B3AF7D02413470E00873C0B /* Enum.hpp */, - 4B3AF7D12413472200873C0B /* Struct.hpp */, - 4B47F6C4241C87A100ED06F7 /* Struct.cpp */, 4BDA7F8129C4C223007A10A5 /* Dispatcher.hpp */, + 4B3AF7D02413470E00873C0B /* Enum.hpp */, + 4B47F6C4241C87A100ED06F7 /* Struct.cpp */, + 4B3AF7D12413472200873C0B /* Struct.hpp */, ); name = Reflection; path = ../../Reflection; @@ -4975,7 +4974,6 @@ 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, 4B80214322EE7C3E00068002 /* JustInTime.hpp */, 4B644ED023F0FB55006C0CC5 /* ScanSynchroniser.hpp */, - 428168382A254FBE008ECD27 /* RangeDispatcher.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, 4B996B2D2496DAC2001660EF /* VSyncPredictor.hpp */, ); diff --git a/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm b/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm index fd273bad7..f8757a8c9 100644 --- a/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm +++ b/OSBindings/Mac/Clock SignalTests/6809OperationMapperTests.mm @@ -9,7 +9,7 @@ #import -#include "RangeDispatcher.hpp" +#include "Dispatcher.hpp" #include "../../../InstructionSets/6809/OperationMapper.hpp" using namespace InstructionSet::M6809; diff --git a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm index ddf21b2be..42c369dc3 100644 --- a/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm +++ b/OSBindings/Mac/Clock SignalTests/DispatcherTests.mm @@ -8,7 +8,7 @@ #import -#include "RangeDispatcher.hpp" +#include "Dispatcher.hpp" #include #include @@ -17,14 +17,7 @@ @interface DispatcherTests : XCTestCase @end -@implementation DispatcherTests { -} - -- (void)setUp { -} - -- (void)tearDown { -} +@implementation DispatcherTests struct DoStep { static constexpr int max = 100; diff --git a/Reflection/Dispatcher.hpp b/Reflection/Dispatcher.hpp index 71829bc56..a3ac2bb13 100644 --- a/Reflection/Dispatcher.hpp +++ b/Reflection/Dispatcher.hpp @@ -9,11 +9,165 @@ #ifndef Dispatcher_hpp #define Dispatcher_hpp +#include #include namespace Reflection { +/*! + Calls @c t.dispatch(args...) as efficiently as possible. +*/ +template void dispatch(TargetT &t, uint8_t c, Args &&... args) { +#define Opt(x) case x: t.template dispatch(std::forward(args)...); break +#define Opt4(x) Opt(x + 0x00); Opt(x + 0x01); Opt(x + 0x02); Opt(x + 0x03) +#define Opt16(x) Opt4(x + 0x00); Opt4(x + 0x04); Opt4(x + 0x08); Opt4(x + 0x0c) +#define Opt64(x) Opt16(x + 0x00); Opt16(x + 0x10); Opt16(x + 0x20); Opt16(x + 0x30) +#define Opt256(x) Opt64(x + 0x00); Opt64(x + 0x40); Opt64(x + 0x80); Opt64(x + 0xc0) + switch(c) { + Opt256(0); + } + +#undef Opt256 +#undef Opt64 +#undef Opt16 +#undef Opt4 +#undef Opt +} + +// Yes, macros, yuck. But I want an actual switch statement for the dispatch to start +// and to allow a simple [[fallthrough]] through all subsequent steps up until end. +// So I don't think I have much in the way of options here. +// +// Sensible choices by the optimiser are assumed. +#define index2(n) index(n); index(n+1); +#define index4(n) index2(n); index2(n+2); +#define index8(n) index4(n); index4(n+4); +#define index16(n) index8(n); index8(n+8); +#define index32(n) index16(n); index16(n+16); +#define index64(n) index32(n); index32(n+32); +#define index128(n) index64(n); index64(n+64); +#define index256(n) index128(n); index128(n+128); +#define index512(n) index256(n); index256(n+256); +#define index1024(n) index512(n); index512(n+512); +#define index2048(n) index1024(n); index1024(n+1024); + +#define switch_indices(x) switch(x) { default: assert(false); index2048(0); } +static constexpr int switch_max = 2048; + +/// Provides glue for a run of calls like: +/// +/// SequencerT.perform<0>(...) +/// SequencerT.perform<1>(...) +/// SequencerT.perform<2>(...) +/// ...etc... +/// +/// Allowing the caller to execute any subrange of the calls. +template +struct RangeDispatcher { + static_assert(SequencerT::max < switch_max); + + /// Perform @c target.perform() for the input range `begin <= n < end`. + template + static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { + + // Minor optimisation: do a comparison with end once outside the loop and if it implies so + // then do no further comparisons within the loop. This is somewhat targetted at expected + // use cases. + if(end < SequencerT::max) { + dispatch(target, begin, end, args...); + } else { + dispatch(target, begin, end, args...); + } + } + + private: + template static void dispatch(SequencerT &target, int begin, int end, Args&&... args) { +#define index(n) \ + case n: \ + if constexpr (n <= SequencerT::max) { \ + if constexpr (n == SequencerT::max) return; \ + if constexpr (n < SequencerT::max) { \ + if(use_end && end == n) return; \ + } \ + target.template perform(args...); \ + } \ + [[fallthrough]]; + + switch_indices(begin); + +#undef index + } + +}; + +/// Uses a classifier to divide a range into typed subranges and issues calls to a target of the form: +/// +/// * begin(location) upon entering a new region; +/// * end(location) upon entering a region; and +/// * advance(distance) in as many chunks as required to populate the inside of the region. +/// +/// @c begin and @c end have iterator-style semantics: begin's location will be the first location in the relevant subrange and +/// end's will be the first location not in the relevant subrange. +template +struct SubrangeDispatcher { + static_assert(ClassifierT::max < switch_max); + + static void dispatch(TargetT &target, int begin, int end) { +#define index(n) \ + case n: \ + if constexpr (n <= ClassifierT::max) { \ + constexpr auto region = ClassifierT::region(n); \ + if constexpr (n == find_begin(n)) { \ + if(n >= end) { \ + return; \ + } \ + target.template begin(n); \ + } \ + if constexpr (n == find_end(n) - 1) { \ + const auto clipped_begin = std::max(begin, find_begin(n)); \ + const auto clipped_end = std::min(end, find_end(n)); \ + target.template advance(clipped_end - clipped_begin); \ + \ + if(clipped_end == n + 1) { \ + target.template end(n + 1); \ + } \ + } \ + } \ + [[fallthrough]]; + + switch_indices(begin); + +#undef index + } + + private: + static constexpr int find_begin(int n) { + const auto type = ClassifierT::region(n); + while(n && ClassifierT::region(n - 1) == type) --n; + return n; + } + + static constexpr int find_end(int n) { + const auto type = ClassifierT::region(n); + while(n < ClassifierT::max && ClassifierT::region(n) == type) ++n; + return n; + } +}; + +#undef switch_indices + +#undef index2 +#undef index4 +#undef index8 +#undef index16 +#undef index32 +#undef index64 +#undef index128 +#undef index256 +#undef index512 +#undef index1024 +#undef index2048 }