summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2021-09-20 03:03:26 +1000
committer Vas Crabb <vas@vastheman.com>2021-09-20 03:03:26 +1000
commit401b22bbf50dbddc45fa99fb92249fd0c86c1bed (patch)
tree76cce80eccf1f99272551fb234d7783d36a6962e /src/emu
parent566a741f4fda9be53504eb9bc965bd9f1649ae2d (diff)
util/delegate.h: Try to catch issues earlier, and some cleanup.
Optimised generation of late bind helper functions. The late bind helper function doesn't depend on the delegate signature - only on the late bind base class and function target class. Having it inside the delegate base class means it needs to be instantiated for every combination of late bind base class, function target class and delegate signature. In a typical driver file, there is only one late bind base class (delegate_late_bind), and there will be delegates with multiple signatures bound to function members of the same class (e.g. read and write handlers, possibly of different widths, bound to members of the driver state class). By moving the late bind helper out of the delegate base class, the number of required instantiations can be reduced. By moving the body out of the enclosing class declaration, the compiler can be encouraged to coalese instantiations across translation units. While this won't give a further reduction in compile time, it should at least reduce the output binary size by reducing duplication for devices that frequently have handlers installed in memory maps. Added an additional template parameter to delegates allowing the late bind base class to be changed if desired. Moved the MSVC implementation "this" pointer optimisation out-of-line and added logging. Also cleaned up the Itanium "this" pointer adjustment and code pointer resolution implementation to reduce duplication and conditional compilation. Made binding_type_exception give a more meaningful what() message than "std::exception". Added extensive validity tests for delegate functionality. Pointers to member functions are tested, including multiple inheritance, virtual and non-virtual member functions, and checking for generational loss across copying/assigning delegates. This should properly exercise "this" pointer adjustment for the Itanium and MSVC implementations, and vtable lookup for the Itanium implementation. So-called late binding functionality is tested, including exceptions on failure. Functoids are tested, although given the encapsulation it's not possible to check that an apator isn't generated when it shouldn't be.
Diffstat (limited to 'src/emu')
-rw-r--r--src/emu/machine.cpp2
-rw-r--r--src/emu/validity.cpp1083
-rw-r--r--src/emu/validity.h10
3 files changed, 787 insertions, 308 deletions
diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp
index 3721ba9bec8..7e9d8091359 100644
--- a/src/emu/machine.cpp
+++ b/src/emu/machine.cpp
@@ -414,7 +414,7 @@ int running_machine::run(bool quiet)
}
catch (binding_type_exception &btex)
{
- osd_printf_error("Error performing a late bind of type %s to %s\n", btex.m_actual_type.name(), btex.m_target_type.name());
+ osd_printf_error("Error performing a late bind of function expecting type %s to instance of type %s\n", btex.target_type().name(), btex.actual_type().name());
error = EMU_ERR_FATALERROR;
}
catch (tag_add_exception &aex)
diff --git a/src/emu/validity.cpp b/src/emu/validity.cpp
index fd8b8d4464b..539b6f3a747 100644
--- a/src/emu/validity.cpp
+++ b/src/emu/validity.cpp
@@ -22,337 +22,114 @@
#include <typeinfo>
-//**************************************************************************
-// TYPE DEFINITIONS
-//**************************************************************************
-
-//**************************************************************************
-// INLINE FUNCTIONS
-//**************************************************************************
-
-//-------------------------------------------------
-// ioport_string_from_index - return an indexed
-// string from the I/O port system
-//-------------------------------------------------
-
-inline const char *validity_checker::ioport_string_from_index(u32 index)
-{
- return ioport_configurer::string_from_token((const char *)(uintptr_t)index);
-}
-
+namespace {
//-------------------------------------------------
-// get_defstr_index - return the index of the
-// string assuming it is one of the default
-// strings
+// diamond_inheritance - forward declaration of a
+// class to force MSVC to use unknown inheritance
+// form of pointers to member functions
//-------------------------------------------------
-inline int validity_checker::get_defstr_index(const char *string, bool suppress_error)
-{
- // check for strings that should be DEF_STR
- auto strindex = m_defstr_map.find(string);
- if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second))
- osd_printf_error("Must use DEF_STR( %s )\n", string);
- return (strindex != m_defstr_map.end()) ? strindex->second : 0;
-}
+class diamond_inheritance;
//-------------------------------------------------
-// random_u64
-// random_s64
-// random_u32
-// random_s32
+// test_delegate - a delegate that can return a
+// result in a register
//-------------------------------------------------
-#undef rand
-inline s32 validity_checker::random_i32() { return s32(random_u32()); }
-inline u32 validity_checker::random_u32() { return rand() ^ (rand() << 15); }
-inline s64 validity_checker::random_i64() { return s64(random_u64()); }
-inline u64 validity_checker::random_u64() { return u64(random_u32()) ^ (u64(random_u32()) << 30); }
+using test_delegate = delegate<char (void const *&)>;
//-------------------------------------------------
-// validate_tag - ensure that the given tag
-// meets the general requirements
+// make_diamond_class_delegate - make a delegate
+// bound to an instance of an incomplete class
+// type
//-------------------------------------------------
-void validity_checker::validate_tag(const char *tag)
+test_delegate make_diamond_class_delegate(char (diamond_inheritance::*func)(void const *&), diamond_inheritance *obj)
{
- // some common names that are now deprecated
- if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0)
- osd_printf_error("Invalid generic tag '%s' used\n", tag);
-
- // scan for invalid characters
- static char const *const validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$";
- for (char const *p = tag; *p; ++p)
- {
- // only lower-case permitted
- if (*p != tolower(u8(*p)))
- {
- osd_printf_error("Tag '%s' contains upper-case characters\n", tag);
- break;
- }
- if (*p == ' ')
- {
- osd_printf_error("Tag '%s' contains spaces\n", tag);
- break;
- }
- if (!strchr(validchars, *p))
- {
- osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p);
- break;
- }
- }
-
- // find the start of the final tag
- const char *begin = strrchr(tag, ':');
- if (begin == nullptr)
- begin = tag;
- else
- begin += 1;
-
- // 0-length = bad
- if (*begin == 0)
- osd_printf_error("Found 0-length tag\n");
-
- // too short/too long = bad
- if (strlen(begin) < MIN_TAG_LENGTH)
- osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH);
+ return test_delegate(func, obj);
}
-
-//**************************************************************************
-// VALIDATION FUNCTIONS
-//**************************************************************************
-
//-------------------------------------------------
-// validity_checker - constructor
+// virtual_base - simple class that will be used
+// as the top vertex of the diamond
//-------------------------------------------------
-validity_checker::validity_checker(emu_options &options, bool quick)
- : m_drivlist(options)
- , m_errors(0)
- , m_warnings(0)
- , m_print_verbose(options.verbose())
- , m_current_driver(nullptr)
- , m_current_device(nullptr)
- , m_current_ioport(nullptr)
- , m_checking_card(false)
- , m_quick(quick)
+struct virtual_base
{
- // pre-populate the defstr map with all the default strings
- for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++)
- {
- const char *string = ioport_string_from_index(strnum);
- if (string != nullptr)
- m_defstr_map.insert(std::make_pair(string, strnum));
- }
-}
-
-//-------------------------------------------------
-// validity_checker - destructor
-//-------------------------------------------------
+ char get_base(void const *&p) { p = this; return 'x'; }
+ int x;
+};
-validity_checker::~validity_checker()
-{
- validate_end();
-}
//-------------------------------------------------
-// check_driver - check a single driver
+// virtual_derived_a - first class derived from
+// virtual base
//-------------------------------------------------
-void validity_checker::check_driver(const game_driver &driver)
+struct virtual_derived_a : virtual virtual_base
{
- // simply validate the one driver
- validate_begin();
- validate_one(driver);
- validate_end();
-}
+ char get_derived_a(void const *&p) { p = this; return 'a'; }
+ int a;
+};
//-------------------------------------------------
-// check_shared_source - check all drivers that
-// share the same source file as the given driver
+// virtual_derived_b - second class derived from
+// virtual base
//-------------------------------------------------
-void validity_checker::check_shared_source(const game_driver &driver)
+struct virtual_derived_b : virtual virtual_base
{
- // initialize
- validate_begin();
-
- // then iterate over all drivers and check the ones that share the same source file
- m_drivlist.reset();
- while (m_drivlist.next())
- if (strcmp(driver.type.source(), m_drivlist.driver().type.source()) == 0)
- validate_one(m_drivlist.driver());
-
- // cleanup
- validate_end();
-}
+ char get_derived_b(void const *&p) { p = this; return 'b'; }
+ int b;
+};
//-------------------------------------------------
-// check_all_matching - check all drivers whose
-// names match the given string
+// diamond_inheritance - actual definition of
+// class with diamond inheritance
//-------------------------------------------------
-bool validity_checker::check_all_matching(const char *string)
+class diamond_inheritance : public virtual_derived_a, public virtual_derived_b
{
- // start by checking core stuff
- validate_begin();
- validate_core();
- validate_inlines();
- validate_rgb();
-
- // if we had warnings or errors, output
- if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty())
- {
- output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings);
- if (m_errors > 0)
- output_indented_errors(m_error_text, "Errors");
- if (m_warnings > 0)
- output_indented_errors(m_warning_text, "Warnings");
- if (!m_verbose_text.empty())
- output_indented_errors(m_verbose_text, "Messages");
- output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
- }
-
- // then iterate over all drivers and check them
- m_drivlist.reset();
- bool validated_any = false;
- while (m_drivlist.next())
- {
- if (driver_list::matches(string, m_drivlist.driver().name))
- {
- validate_one(m_drivlist.driver());
- validated_any = true;
- }
- }
-
- // validate devices
- if (!string)
- validate_device_types();
-
- // cleanup
- validate_end();
-
- // if we failed to match anything, it
- if (string && !validated_any)
- throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", string);
-
- return !(m_errors > 0 || m_warnings > 0);
-}
-
-
-//-------------------------------------------------
-// validate_begin - prepare for validation by
-// taking over the output callbacks and resetting
-// our internal state
-//-------------------------------------------------
-
-void validity_checker::validate_begin()
-{
- // take over error and warning outputs
- osd_output::push(this);
-
- // reset all our maps
- m_names_map.clear();
- m_descriptions_map.clear();
- m_roms_map.clear();
- m_defstr_map.clear();
- m_region_map.clear();
- m_ioport_set.clear();
-
- // reset internal state
- m_errors = 0;
- m_warnings = 0;
- m_already_checked.clear();
-}
+};
//-------------------------------------------------
-// validate_end - restore output callbacks and
-// clean up
+// ioport_string_from_index - return an indexed
+// string from the I/O port system
//-------------------------------------------------
-void validity_checker::validate_end()
+inline char const *ioport_string_from_index(u32 index)
{
- // restore the original output callbacks
- osd_output::pop(this);
+ return ioport_configurer::string_from_token(reinterpret_cast<char const *>(uintptr_t(index)));
}
//-------------------------------------------------
-// validate_drivers - master validity checker
+// random_u64
+// random_s64
+// random_u32
+// random_s32
//-------------------------------------------------
-
-void validity_checker::validate_one(const game_driver &driver)
-{
- // help verbose validation detect configuration-related crashes
- if (m_print_verbose)
- output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating driver %s (%s)...\n", driver.name, core_filename_extract_base(driver.type.source()));
-
- // set the current driver
- m_current_driver = &driver;
- m_current_device = nullptr;
- m_current_ioport = nullptr;
- m_region_map.clear();
- m_ioport_set.clear();
- m_checking_card = false;
-
- // reset error/warning state
- int start_errors = m_errors;
- int start_warnings = m_warnings;
- m_error_text.clear();
- m_warning_text.clear();
- m_verbose_text.clear();
-
- // wrap in try/catch to catch fatalerrors
- try
- {
- machine_config config(driver, m_blank_options);
- validate_driver(config.root_device());
- validate_roms(config.root_device());
- validate_inputs(config.root_device());
- validate_devices(config);
- }
- catch (emu_fatalerror const &err)
- {
- osd_printf_error("Fatal error %s", err.what());
- }
-
- // if we had warnings or errors, output
- if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty())
- {
- if (!m_print_verbose)
- output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): ", driver.name, core_filename_extract_base(driver.type.source()));
- output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings);
- if (m_errors > start_errors)
- output_indented_errors(m_error_text, "Errors");
- if (m_warnings > start_warnings)
- output_indented_errors(m_warning_text, "Warnings");
- if (!m_verbose_text.empty())
- output_indented_errors(m_verbose_text, "Messages");
- output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
- }
-
- // reset the driver/device
- m_current_driver = nullptr;
- m_current_device = nullptr;
- m_current_ioport = nullptr;
- m_region_map.clear();
- m_ioport_set.clear();
- m_checking_card = false;
-}
+#undef rand
+inline u32 random_u32() { return rand() ^ (rand() << 15); }
+inline s32 random_i32() { return s32(random_u32()); }
+inline u64 random_u64() { return u64(random_u32()) ^ (u64(random_u32()) << 30); }
+inline s64 random_i64() { return s64(random_u64()); }
//-------------------------------------------------
-// validate_core - validate core internal systems
+// validate_integer_semantics - validate that
+// integers behave as expected, particularly
+// with regards to overflow and shifting
//-------------------------------------------------
-void validity_checker::validate_core()
+void validate_integer_semantics()
{
// basic system checks
if (~0 != -1) osd_printf_error("Machine must be two's complement\n");
@@ -405,7 +182,7 @@ void validity_checker::validate_core()
// behaviors
//-------------------------------------------------
-void validity_checker::validate_inlines()
+void validate_inlines()
{
volatile u64 testu64a = random_u64();
volatile s64 testi64a = random_i64();
@@ -536,7 +313,7 @@ void validity_checker::validate_inlines()
// class
//-------------------------------------------------
-void validity_checker::validate_rgb()
+void validate_rgb()
{
/*
This performs cursory tests of most of the vector-optimised RGB
@@ -566,29 +343,31 @@ void validity_checker::validate_rgb()
scale_imm_add_and_clamp(const s32, const rgbaint_t&);
*/
- auto random_i32_nolimit = [this]
- {
- s32 result;
- do { result = random_i32(); } while ((result == std::numeric_limits<s32>::min()) || (result == std::numeric_limits<s32>::max()));
- return result;
- };
+ auto random_i32_nolimit =
+ [] ()
+ {
+ s32 result;
+ do { result = random_i32(); } while ((result == std::numeric_limits<s32>::min()) || (result == std::numeric_limits<s32>::max()));
+ return result;
+ };
volatile s32 expected_a, expected_r, expected_g, expected_b;
volatile s32 actual_a, actual_r, actual_g, actual_b;
volatile s32 imm;
rgbaint_t rgb, other;
rgb_t packed;
- auto check_expected = [&] (const char *desc)
- {
- const volatile s32 a = rgb.get_a32();
- const volatile s32 r = rgb.get_r32();
- const volatile s32 g = rgb.get_g32();
- const volatile s32 b = rgb.get_b32();
- if (a != expected_a) osd_printf_error("Error testing %s get_a32() = %d (expected %d)\n", desc, s32(a), s32(expected_a));
- if (r != expected_r) osd_printf_error("Error testing %s get_r32() = %d (expected %d)\n", desc, s32(r), s32(expected_r));
- if (g != expected_g) osd_printf_error("Error testing %s get_g32() = %d (expected %d)\n", desc, s32(g), s32(expected_g));
- if (b != expected_b) osd_printf_error("Error testing %s get_b32() = %d (expected %d)\n", desc, s32(b), s32(expected_b));
- };
+ auto check_expected =
+ [&] (const char *desc)
+ {
+ const volatile s32 a = rgb.get_a32();
+ const volatile s32 r = rgb.get_r32();
+ const volatile s32 g = rgb.get_g32();
+ const volatile s32 b = rgb.get_b32();
+ if (a != expected_a) osd_printf_error("Error testing %s get_a32() = %d (expected %d)\n", desc, s32(a), s32(expected_a));
+ if (r != expected_r) osd_printf_error("Error testing %s get_r32() = %d (expected %d)\n", desc, s32(r), s32(expected_r));
+ if (g != expected_g) osd_printf_error("Error testing %s get_g32() = %d (expected %d)\n", desc, s32(g), s32(expected_g));
+ if (b != expected_b) osd_printf_error("Error testing %s get_b32() = %d (expected %d)\n", desc, s32(b), s32(expected_b));
+ };
// check set/get
expected_a = random_i32();
@@ -1454,6 +1233,716 @@ void validity_checker::validate_rgb()
//-------------------------------------------------
+// validate_delegates_mfp - test delegate member
+// function functionality
+//-------------------------------------------------
+
+void validate_delegates_mfp()
+{
+ struct base_a
+ {
+ virtual ~base_a() = default;
+ char get_a(void const *&p) { p = this; return 'a'; }
+ virtual char get_a_v(void const *&p) { p = this; return 'A'; }
+ int a;
+ };
+
+ struct base_b
+ {
+ virtual ~base_b() = default;
+ char get_b(void const *&p) { p = this; return 'b'; }
+ virtual char get_b_v(void const *&p) { p = this; return 'B'; }
+ int b;
+ };
+
+ struct multiple_inheritance : base_a, base_b
+ {
+ };
+
+ struct overridden : base_a, base_b
+ {
+ virtual char get_a_v(void const *&p) override { p = this; return 'x'; }
+ virtual char get_b_v(void const *&p) override { p = this; return 'y'; }
+ };
+
+ multiple_inheritance mi;
+ overridden o;
+ diamond_inheritance d;
+ char ch;
+ void const *addr;
+
+ // test non-virtual member functions and "this" pointer adjustment
+ test_delegate cb1(&multiple_inheritance::get_a, &mi);
+ test_delegate cb2(&multiple_inheritance::get_b, &mi);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test that "this" pointer adjustment survives copy construction
+ test_delegate cb3(cb1);
+ test_delegate cb4(cb2);
+
+ addr = nullptr;
+ ch = cb3(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing copy constructed delegate non-virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb4(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing copy constructed delegate non-virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test that "this" pointer adjustment survives assignment and doesn't suffer generational loss
+ cb1 = cb4;
+ cb2 = cb3;
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing assigned delegate non-virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing assigned delegate non-virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ // test virtual member functions and "this" pointer adjustment
+ cb1 = test_delegate(&multiple_inheritance::get_a_v, &mi);
+ cb2 = test_delegate(&multiple_inheritance::get_b_v, &mi);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('A' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('B' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test that virtual member functions survive copy construction
+ test_delegate cb5(cb1);
+ test_delegate cb6(cb2);
+
+ addr = nullptr;
+ ch = cb5(addr);
+ if ('A' != ch)
+ osd_printf_error("Error testing copy constructed delegate virtual member function dispatch\n");
+ if (static_cast<base_a *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_a *>(&mi)));
+
+ addr = nullptr;
+ ch = cb6(addr);
+ if ('B' != ch)
+ osd_printf_error("Error testing copy constructed delegate virtual member function dispatch\n");
+ if (static_cast<base_b *>(&mi) != addr)
+ osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast<void const *>(&mi), addr, static_cast<void const *>(static_cast<base_b *>(&mi)));
+
+ // test virtual member function dispatch through base pointer
+ cb1 = test_delegate(&base_a::get_a_v, static_cast<base_a *>(&o));
+ cb2 = test_delegate(&base_b::get_b_v, static_cast<base_b *>(&o));
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('x' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch through base class pointer\n");
+ if (&o != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function through base class pointer %p -> %p (expected %p)\n", static_cast<void const *>(static_cast<base_a *>(&o)), addr, static_cast<void const *>(&o));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('y' != ch)
+ osd_printf_error("Error testing delegate virtual member function dispatch through base class pointer\n");
+ if (&o != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for virtual member function through base class pointer %p -> %p (expected %p)\n", static_cast<void const *>(static_cast<base_b *>(&o)), addr, static_cast<void const *>(&o));
+
+ // test creating delegates for a forward-declared class
+ cb1 = make_diamond_class_delegate(&diamond_inheritance::get_derived_a, &d);
+ cb2 = make_diamond_class_delegate(&diamond_inheritance::get_derived_b, &d);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('a' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n");
+ if (static_cast<virtual_derived_a *>(&d) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_derived_b *>(&d)));
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if ('b' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n");
+ if (static_cast<virtual_derived_b *>(&d) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_derived_b *>(&d)));
+
+ // test MSVC extension allowing casting member pointer types across virtual inheritance relationships
+#if defined(_MSC_VER)
+ cb1 = make_diamond_class_delegate(&diamond_inheritance::get_base, &d);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if ('x' != ch)
+ osd_printf_error("Error testing delegate non-virtual member function dispatch for incomplete class\n");
+ if (static_cast<virtual_base *>(&d) != addr)
+ osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast<void const *>(&d), addr, static_cast<void const *>(static_cast<virtual_base *>(&d)));
+#endif // defined(_MSC_VER)
+}
+
+
+//-------------------------------------------------
+// validate_delegates_latebind - test binding a
+// delegate to an object after the function is
+// set
+//-------------------------------------------------
+
+void validate_delegates_latebind()
+{
+ struct target
+ {
+ virtual ~target() = default;
+ virtual char operator()(void const *&p) const = 0;
+ };
+
+ struct derived_a : target, delegate_late_bind
+ {
+ virtual char operator()(void const *&p) const override { p = this; return 'a'; }
+ };
+
+ struct derived_b : target, delegate_late_bind
+ {
+ virtual char operator()(void const *&p) const override { p = this; return 'b'; }
+ };
+
+ struct unrelated : delegate_late_bind
+ {
+ };
+
+ char ch;
+ void const *addr;
+ derived_a a;
+ derived_b b;
+ unrelated u;
+
+ // delegate with no target object
+ test_delegate cb1(&target::operator(), static_cast<target *>(nullptr));
+
+ // test late bind on construction
+ test_delegate cb2(cb1, a);
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('a' != ch) || (&a != addr))
+ osd_printf_error("Error testing delegate late bind on construction\n");
+
+ // test explicit late bind
+ cb1.late_bind(b);
+ ch = cb1(addr);
+ if (('b' != ch) || (&b != addr))
+ osd_printf_error("Error testing delegate explicit late bind\n");
+
+ // test late bind when object is set
+ cb1.late_bind(a);
+ ch = cb1(addr);
+ if (('a' != ch) || (&a != addr))
+ osd_printf_error("Error testing delegate explicit late bind when object is set\n");
+
+ // test late bind on copy of delegate with target set
+ test_delegate cb3(cb1, b);
+ addr = nullptr;
+ ch = cb3(addr);
+ if (('b' != ch) || (&b != addr))
+ osd_printf_error("Error testing delegate late bind on construction using template with object set\n");
+
+ // test late bind exception
+ ch = '-';
+ try
+ {
+ cb1.late_bind(u);
+ }
+ catch (binding_type_exception const &e)
+ {
+ if ((e.target_type() != typeid(target)) || (e.actual_type() != typeid(unrelated)))
+ {
+ osd_printf_error(
+ "Error testing delegate late bind type error %s -> %s (expected %s -> %s)\n",
+ e.actual_type().name(),
+ e.target_type().name(),
+ typeid(unrelated).name(),
+ typeid(target).name());
+ }
+ ch = '+';
+ }
+ if ('+' != ch)
+ osd_printf_error("Error testing delegate late bind type error\n");
+
+ // test syntax for creating delegate with alternate late bind base
+ delegate<char (void const *&), target> cb4(
+ [] (auto &o, void const *&p) { p = &o; return 'l'; },
+ static_cast<unrelated *>(nullptr));
+ try { cb1.late_bind(a); }
+ catch (binding_type_exception const &) { }
+}
+
+
+//-------------------------------------------------
+// validate_delegates_functoid - test delegate
+// functoid functionality
+//-------------------------------------------------
+
+void validate_delegates_functoid()
+{
+ using void_delegate = delegate<void (void const *&)>;
+ struct const_only
+ {
+ char operator()(void const *&p) const { return 'C'; }
+ };
+
+ struct const_or_not
+ {
+ char operator()(void const *&p) { return 'n'; }
+ char operator()(void const *&p) const { return 'c'; }
+ };
+
+ struct noncopyable
+ {
+ noncopyable() = default;
+ noncopyable(noncopyable const &) = delete;
+ noncopyable &operator=(noncopyable const &) = delete;
+
+ char operator()(void const *&p) { p = this; return '*'; }
+ };
+
+ noncopyable n;
+ char ch;
+ void const *addr = nullptr;
+
+ // test that const call operators are supported
+ test_delegate cb1{ const_only() };
+ if ('C' != cb1(addr))
+ osd_printf_error("Error testing delegate functoid dispatch\n");
+
+ // test that non-const call operators are preferred
+ cb1 = test_delegate{ const_or_not() };
+ if ('n' != cb1(addr))
+ osd_printf_error("Error testing delegate functoid dispatch\n");
+
+ // test that functoids are implicitly mutable
+ cb1 = test_delegate{ [a = &addr, c = '0'] (void const *&p) mutable { p = a; return c++; } };
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('0' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 0)\n", ch);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('1' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 1)\n", ch);
+
+ // test that functoids survive copy construction
+ test_delegate cb2(cb1);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('2' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 2)\n", ch);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('3' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 3)\n", ch);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('2' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 2)\n", ch);
+
+ // test that functoids survive assignment
+ cb1 = cb2;
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('4' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 4)\n", ch);
+
+ addr = nullptr;
+ ch = cb1(addr);
+ if (('5' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 5)\n", ch);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('4' != ch) || (&addr != addr))
+ osd_printf_error("Error testing delegate functoid %c (expected 4)\n", ch);
+
+ // test that std::ref can be used with non-copyable functoids
+ test_delegate cb3(std::ref(n));
+
+ addr = nullptr;
+ ch = cb3(addr);
+ if (('*' != ch) || (&n != addr))
+ osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ // test that std::ref survives copy construction and assignment
+ cb2 = cb3;
+ test_delegate cb4(cb3);
+
+ addr = nullptr;
+ ch = cb2(addr);
+ if (('*' != ch) || (&n != addr))
+ osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ addr = nullptr;
+ ch = cb4(addr);
+ if (('*' != ch) || (&n != addr))
+ osd_printf_error("Error testing delegate with functoid reference wrapper %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ // test discarding return value for delegates returning void
+ void_delegate void_cb1{ [&cb1] (void const *&p) { p = &cb1; return 123; } };
+ void_delegate void_cb2{ std::ref(n) };
+
+ addr = nullptr;
+ void_cb1(addr);
+ if (&cb1 != addr)
+ osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast<void const *>(&cb1));
+
+ addr = nullptr;
+ void_cb2(addr);
+ if (&n != addr)
+ osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast<void const *>(&n));
+
+ // test that adaptor is generated after assignment
+ void_cb2 = void_cb1;
+
+ addr = nullptr;
+ void_cb2(addr);
+ if (&cb1 != addr)
+ osd_printf_error("Error testing delegate with functoid requiring adapter %p (expected %p)\n", addr, static_cast<void const *>(&cb1));
+}
+
+} // anonymous namespace
+
+
+
+//-------------------------------------------------
+// get_defstr_index - return the index of the
+// string assuming it is one of the default
+// strings
+//-------------------------------------------------
+
+inline int validity_checker::get_defstr_index(const char *string, bool suppress_error)
+{
+ // check for strings that should be DEF_STR
+ auto strindex = m_defstr_map.find(string);
+ if (!suppress_error && strindex != m_defstr_map.end() && string != ioport_string_from_index(strindex->second))
+ osd_printf_error("Must use DEF_STR( %s )\n", string);
+ return (strindex != m_defstr_map.end()) ? strindex->second : 0;
+}
+
+
+
+//-------------------------------------------------
+// validate_tag - ensure that the given tag
+// meets the general requirements
+//-------------------------------------------------
+
+void validity_checker::validate_tag(const char *tag)
+{
+ // some common names that are now deprecated
+ if (strcmp(tag, "main") == 0 || strcmp(tag, "audio") == 0 || strcmp(tag, "sound") == 0 || strcmp(tag, "left") == 0 || strcmp(tag, "right") == 0)
+ osd_printf_error("Invalid generic tag '%s' used\n", tag);
+
+ // scan for invalid characters
+ static char const *const validchars = "abcdefghijklmnopqrstuvwxyz0123456789_.:^$";
+ for (char const *p = tag; *p; ++p)
+ {
+ // only lower-case permitted
+ if (*p != tolower(u8(*p)))
+ {
+ osd_printf_error("Tag '%s' contains upper-case characters\n", tag);
+ break;
+ }
+ if (*p == ' ')
+ {
+ osd_printf_error("Tag '%s' contains spaces\n", tag);
+ break;
+ }
+ if (!strchr(validchars, *p))
+ {
+ osd_printf_error("Tag '%s' contains invalid character '%c'\n", tag, *p);
+ break;
+ }
+ }
+
+ // find the start of the final tag
+ const char *begin = strrchr(tag, ':');
+ if (begin == nullptr)
+ begin = tag;
+ else
+ begin += 1;
+
+ // 0-length = bad
+ if (*begin == 0)
+ osd_printf_error("Found 0-length tag\n");
+
+ // too short/too long = bad
+ if (strlen(begin) < MIN_TAG_LENGTH)
+ osd_printf_error("Tag '%s' is too short (must be at least %d characters)\n", tag, MIN_TAG_LENGTH);
+}
+
+
+
+//**************************************************************************
+// VALIDATION FUNCTIONS
+//**************************************************************************
+
+//-------------------------------------------------
+// validity_checker - constructor
+//-------------------------------------------------
+
+validity_checker::validity_checker(emu_options &options, bool quick)
+ : m_drivlist(options)
+ , m_errors(0)
+ , m_warnings(0)
+ , m_print_verbose(options.verbose())
+ , m_current_driver(nullptr)
+ , m_current_device(nullptr)
+ , m_current_ioport(nullptr)
+ , m_checking_card(false)
+ , m_quick(quick)
+{
+ // pre-populate the defstr map with all the default strings
+ for (int strnum = 1; strnum < INPUT_STRING_COUNT; strnum++)
+ {
+ const char *string = ioport_string_from_index(strnum);
+ if (string != nullptr)
+ m_defstr_map.insert(std::make_pair(string, strnum));
+ }
+}
+
+//-------------------------------------------------
+// validity_checker - destructor
+//-------------------------------------------------
+
+validity_checker::~validity_checker()
+{
+ validate_end();
+}
+
+//-------------------------------------------------
+// check_driver - check a single driver
+//-------------------------------------------------
+
+void validity_checker::check_driver(const game_driver &driver)
+{
+ // simply validate the one driver
+ validate_begin();
+ validate_one(driver);
+ validate_end();
+}
+
+
+//-------------------------------------------------
+// check_shared_source - check all drivers that
+// share the same source file as the given driver
+//-------------------------------------------------
+
+void validity_checker::check_shared_source(const game_driver &driver)
+{
+ // initialize
+ validate_begin();
+
+ // then iterate over all drivers and check the ones that share the same source file
+ m_drivlist.reset();
+ while (m_drivlist.next())
+ if (strcmp(driver.type.source(), m_drivlist.driver().type.source()) == 0)
+ validate_one(m_drivlist.driver());
+
+ // cleanup
+ validate_end();
+}
+
+
+//-------------------------------------------------
+// check_all_matching - check all drivers whose
+// names match the given string
+//-------------------------------------------------
+
+bool validity_checker::check_all_matching(const char *string)
+{
+ // start by checking core stuff
+ validate_begin();
+ validate_integer_semantics();
+ validate_inlines();
+ validate_rgb();
+ validate_delegates_mfp();
+ validate_delegates_latebind();
+ validate_delegates_functoid();
+
+ // if we had warnings or errors, output
+ if (m_errors > 0 || m_warnings > 0 || !m_verbose_text.empty())
+ {
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Core: %d errors, %d warnings\n", m_errors, m_warnings);
+ if (m_errors > 0)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > 0)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+
+ // then iterate over all drivers and check them
+ m_drivlist.reset();
+ bool validated_any = false;
+ while (m_drivlist.next())
+ {
+ if (driver_list::matches(string, m_drivlist.driver().name))
+ {
+ validate_one(m_drivlist.driver());
+ validated_any = true;
+ }
+ }
+
+ // validate devices
+ if (!string)
+ validate_device_types();
+
+ // cleanup
+ validate_end();
+
+ // if we failed to match anything, it
+ if (string && !validated_any)
+ throw emu_fatalerror(EMU_ERR_NO_SUCH_SYSTEM, "No matching systems found for '%s'", string);
+
+ return !(m_errors > 0 || m_warnings > 0);
+}
+
+
+//-------------------------------------------------
+// validate_begin - prepare for validation by
+// taking over the output callbacks and resetting
+// our internal state
+//-------------------------------------------------
+
+void validity_checker::validate_begin()
+{
+ // take over error and warning outputs
+ osd_output::push(this);
+
+ // reset all our maps
+ m_names_map.clear();
+ m_descriptions_map.clear();
+ m_roms_map.clear();
+ m_defstr_map.clear();
+ m_region_map.clear();
+ m_ioport_set.clear();
+
+ // reset internal state
+ m_errors = 0;
+ m_warnings = 0;
+ m_already_checked.clear();
+}
+
+
+//-------------------------------------------------
+// validate_end - restore output callbacks and
+// clean up
+//-------------------------------------------------
+
+void validity_checker::validate_end()
+{
+ // restore the original output callbacks
+ osd_output::pop(this);
+}
+
+
+//-------------------------------------------------
+// validate_drivers - master validity checker
+//-------------------------------------------------
+
+void validity_checker::validate_one(const game_driver &driver)
+{
+ // help verbose validation detect configuration-related crashes
+ if (m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Validating driver %s (%s)...\n", driver.name, core_filename_extract_base(driver.type.source()));
+
+ // set the current driver
+ m_current_driver = &driver;
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+ m_region_map.clear();
+ m_ioport_set.clear();
+ m_checking_card = false;
+
+ // reset error/warning state
+ int start_errors = m_errors;
+ int start_warnings = m_warnings;
+ m_error_text.clear();
+ m_warning_text.clear();
+ m_verbose_text.clear();
+
+ // wrap in try/catch to catch fatalerrors
+ try
+ {
+ machine_config config(driver, m_blank_options);
+ validate_driver(config.root_device());
+ validate_roms(config.root_device());
+ validate_inputs(config.root_device());
+ validate_devices(config);
+ }
+ catch (emu_fatalerror const &err)
+ {
+ osd_printf_error("Fatal error %s", err.what());
+ }
+
+ // if we had warnings or errors, output
+ if (m_errors > start_errors || m_warnings > start_warnings || !m_verbose_text.empty())
+ {
+ if (!m_print_verbose)
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "Driver %s (file %s): ", driver.name, core_filename_extract_base(driver.type.source()));
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "%d errors, %d warnings\n", m_errors - start_errors, m_warnings - start_warnings);
+ if (m_errors > start_errors)
+ output_indented_errors(m_error_text, "Errors");
+ if (m_warnings > start_warnings)
+ output_indented_errors(m_warning_text, "Warnings");
+ if (!m_verbose_text.empty())
+ output_indented_errors(m_verbose_text, "Messages");
+ output_via_delegate(OSD_OUTPUT_CHANNEL_ERROR, "\n");
+ }
+
+ // reset the driver/device
+ m_current_driver = nullptr;
+ m_current_device = nullptr;
+ m_current_ioport = nullptr;
+ m_region_map.clear();
+ m_ioport_set.clear();
+ m_checking_card = false;
+}
+
+
+//-------------------------------------------------
// validate_driver - validate basic driver
// information
//-------------------------------------------------
@@ -1822,16 +2311,16 @@ void validity_checker::validate_dip_settings(const ioport_field &field)
auto const nextsetting = std::next(setting);
if (field.settings().end() != nextsetting)
{
- // check for inverted off/on dispswitch order
+ // check for inverted off/on DIP switch order
int next_strindex = get_defstr_index(nextsetting->name(), true);
if (strindex == INPUT_STRING_On && next_strindex == INPUT_STRING_Off)
osd_printf_error("%s option must have Off/On options in the order: Off, On\n", field.name());
- // check for inverted yes/no dispswitch order
+ // check for inverted yes/no DIP switch order
else if (strindex == INPUT_STRING_Yes && next_strindex == INPUT_STRING_No)
osd_printf_error("%s option must have Yes/No options in the order: No, Yes\n", field.name());
- // check for inverted upright/cocktail dispswitch order
+ // check for inverted upright/cocktail DIP switch order
else if (strindex == INPUT_STRING_Cocktail && next_strindex == INPUT_STRING_Upright)
osd_printf_error("%s option must have Upright/Cocktail options in the order: Upright, Cocktail\n", field.name());
diff --git a/src/emu/validity.h b/src/emu/validity.h
index bdb30b409e4..3e6ff8e5a00 100644
--- a/src/emu/validity.h
+++ b/src/emu/validity.h
@@ -60,7 +60,6 @@ private:
using string_set = std::unordered_set<std::string>;
// internal helpers
- const char *ioport_string_from_index(u32 index);
int get_defstr_index(const char *string, bool suppress_error = false);
// core helpers
@@ -69,9 +68,6 @@ private:
void validate_one(const game_driver &driver);
// internal sub-checks
- void validate_core();
- void validate_inlines();
- void validate_rgb();
void validate_driver(device_t &root);
void validate_roms(device_t &root);
void validate_analog_input_field(const ioport_field &field);
@@ -86,12 +82,6 @@ private:
template <typename Format, typename... Params> void output_via_delegate(osd_output_channel channel, Format &&fmt, Params &&...args);
void output_indented_errors(std::string &text, const char *header);
- // random number generation
- s32 random_i32();
- u32 random_u32();
- s64 random_i64();
- u64 random_u64();
-
// internal driver list
driver_enumerator m_drivlist;