diff options
author | 2021-09-20 03:03:26 +1000 | |
---|---|---|
committer | 2021-09-20 03:03:26 +1000 | |
commit | 401b22bbf50dbddc45fa99fb92249fd0c86c1bed (patch) | |
tree | 76cce80eccf1f99272551fb234d7783d36a6962e /src | |
parent | 566a741f4fda9be53504eb9bc965bd9f1649ae2d (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')
-rw-r--r-- | src/emu/machine.cpp | 2 | ||||
-rw-r--r-- | src/emu/validity.cpp | 1083 | ||||
-rw-r--r-- | src/emu/validity.h | 10 | ||||
-rw-r--r-- | src/lib/util/delegate.cpp | 119 | ||||
-rw-r--r-- | src/lib/util/delegate.h | 233 | ||||
-rw-r--r-- | src/osd/eigccppc.h | 8 |
6 files changed, 1007 insertions, 448 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; diff --git a/src/lib/util/delegate.cpp b/src/lib/util/delegate.cpp index 4b4812d3c56..ffe3b060784 100644 --- a/src/lib/util/delegate.cpp +++ b/src/lib/util/delegate.cpp @@ -11,31 +11,54 @@ #include "delegate.h" #include <cstdio> +#include <sstream> //************************************************************************** // MACROS //************************************************************************** -#if defined(LOG_DELEGATES) -#define LOG(...) printf(__VA_ARGS__) +#if defined(MAME_DELEGATE_LOG_ADJ) + #define LOG(...) printf(__VA_ARGS__) #else -#define LOG(...) do { if (false) printf(__VA_ARGS__); } while (false) + #define LOG(...) do { if (false) printf(__VA_ARGS__); } while (false) #endif -// some architectures use function descriptor pointers +// on some architectures, function pointers point to descriptors // usually this is a global pointer value along with the branch target // other platforms using this convention include: // * AIX, Classic MacOS and WinNT on 32-bit POWER/PowerPC // * pretty much anything on Itanium #if (defined(__ppc64__) || (defined(__PPC64__))) && !defined(__APPLE__) -#define MAME_DELEGATE_VT_DESCRIPTOR 1 + #define MAME_DELEGATE_VT_DESCRIPTOR 1 #endif #ifndef MAME_DELEGATE_VT_DESCRIPTOR -#define MAME_DELEGATE_VT_DESCRIPTOR 0 + #define MAME_DELEGATE_VT_DESCRIPTOR 0 #endif + +//************************************************************************** +// LATE BINDING EXCEPTION +//************************************************************************** + +binding_type_exception::binding_type_exception(std::type_info const &target_type, std::type_info const &actual_type) + : m_target_type(&target_type) + , m_actual_type(&actual_type) +{ + std::ostringstream os; + os << "Error performing late bind of function expecting type " << target_type.name() << " to instance of type " << actual_type.name(); + m_what = os.str(); +} + + +char const *binding_type_exception::what() const noexcept +{ + return m_what.c_str(); +} + + + namespace util::detail { //************************************************************************** @@ -51,55 +74,65 @@ const delegate_mfp_compatible::raw_mfp_data delegate_mfp_compatible::s_null_mfp //************************************************************************** //------------------------------------------------- -// delegate_convert_raw - given an object and an raw function, adjust the object base -// and return the actual final code pointer -//-------------------------------------------------// +// delegate_mfp_itanium::convert_to_generic - +// given an object pointer and member function +// pointer, apply the displacement and get the +// actual function pointer +//------------------------------------------------- delegate_generic_function delegate_mfp_itanium::convert_to_generic(delegate_generic_class *&object) const { -#if MAME_DELEGATE_ITANIUM_ARM - // apply the "this" delta to the object first - remember to shift right one bit position - object = reinterpret_cast<delegate_generic_class *>(reinterpret_cast<std::uint8_t *>(object) + (m_this_delta >> 1)); - if (!(m_this_delta & 1)) + // apply the "this" delta to the object first - the value is shifted to the left one bit position for the ARM-like variant + LOG("Input this=%p ptr=%p adj=%ld ", reinterpret_cast<void const *>(object), reinterpret_cast<void const *>(m_function), long(m_this_delta)); + object = reinterpret_cast<delegate_generic_class *>( + reinterpret_cast<std::uint8_t *>(object) + (m_this_delta >> (MAME_DELEGATE_ITANIUM_ARM ? 1 : 0))); + LOG("Calculated this=%p ", reinterpret_cast<void const *>(object)); + + // test the virtual member function flag - it's the low bit of either the ptr or adj field, depending on the variant + if (MAME_DELEGATE_ITANIUM_ARM ? !(m_this_delta & 1) : !(m_function & 1)) { - // if the low bit of the 'this' delta is clear, the pointer is a conventional function pointer - LOG("Calculated Addr = %p\n", reinterpret_cast<void const *>(m_function)); + // conventional function pointer + LOG("ptr=%p\n", reinterpret_cast<void const *>(m_function)); return reinterpret_cast<delegate_generic_function>(m_function); } else { - // otherwise, it is the byte index into the vtable where the actual function lives - std::uint8_t const *const vtable_ptr = *reinterpret_cast<std::uint8_t const *const *>(object) + m_function; -#if MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = reinterpret_cast<delegate_generic_function>(uintptr_t(vtable_ptr)); -#else // MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = *reinterpret_cast<delegate_generic_function const *>(vtable_ptr); -#endif // MAME_DELEGATE_VT_DESCRIPTOR - LOG("Calculated Addr = %p (VTAB)\n", reinterpret_cast<void const *>(uintptr_t(result))); + // byte index into the vtable to the function + std::uint8_t const *const vtable_ptr = *reinterpret_cast<std::uint8_t const *const *>(object) + m_function - (MAME_DELEGATE_ITANIUM_ARM ? 0 : 1); + delegate_generic_function result; + if (MAME_DELEGATE_VT_DESCRIPTOR) + result = reinterpret_cast<delegate_generic_function>(uintptr_t(vtable_ptr)); + else + result = *reinterpret_cast<delegate_generic_function const *>(vtable_ptr); + LOG("ptr=%p (vtable)\n", reinterpret_cast<void const *>(result)); return result; } -#else // MAME_DELEGATE_ITANIUM_ARM - // apply the "this" delta to the object first - object = reinterpret_cast<delegate_generic_class *>(reinterpret_cast<std::uint8_t *>(object) + m_this_delta); - if (!(m_function & 1)) - { - // if the low bit of the pointer is clear, then it is a conventional function pointer - LOG("Calculated Addr = %p\n", reinterpret_cast<void const *>(m_function)); - return reinterpret_cast<delegate_generic_function>(m_function); - } - else +} + + +//------------------------------------------------- +// delegate_mfp_msvc::adjust_this_pointer - given +// an object pointer and member function pointer, +// apply the displacement +//------------------------------------------------- + +void delegate_mfp_msvc::adjust_this_pointer(delegate_generic_class *&object) const +{ + LOG("Input this=%p ", reinterpret_cast<void const *>(object)); + if (sizeof(single_base_equiv) < m_size) + LOG("thisdelta=%d ", m_this_delta); + if (sizeof(unknown_base_equiv) == m_size) + LOG("vptrdelta=%d vindex=%d ", m_vptr_offs, m_vt_index); + std::uint8_t *byteptr = reinterpret_cast<std::uint8_t *>(object); + if ((sizeof(unknown_base_equiv) == m_size) && m_vt_index) { - // otherwise, it is the byte index into the vtable where the actual function lives - std::uint8_t const *const vtable_ptr = *reinterpret_cast<std::uint8_t const *const *>(object) + m_function - 1; -#if MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = reinterpret_cast<delegate_generic_function>(uintptr_t(vtable_ptr)); -#else // MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = *reinterpret_cast<delegate_generic_function const *>(vtable_ptr); -#endif // MAME_DELEGATE_VT_DESCRIPTOR - LOG("Calculated Addr = %p (VTAB)\n", reinterpret_cast<void const *>(uintptr_t(result))); - return result; + std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(byteptr + m_vptr_offs); + byteptr += *reinterpret_cast<int const *>(vptr + m_vt_index); } -#endif // MAME_DELEGATE_ITANIUM_ARM + if (sizeof(single_base_equiv) < m_size) + byteptr += m_this_delta; + LOG("Calculated this=%p\n", reinterpret_cast<void const *>(byteptr)); + object = reinterpret_cast<delegate_generic_class *>(byteptr); } } // namespace util::detail diff --git a/src/lib/util/delegate.h b/src/lib/util/delegate.h index 2d5d1afbdf7..e1d5ae94230 100644 --- a/src/lib/util/delegate.h +++ b/src/lib/util/delegate.h @@ -33,7 +33,7 @@ The "compatible" version of delegates is based on an implementation from Sergey Ryazanov, found here: - http://www.codeproject.com/KB/cpp/ImpossiblyFastCppDelegate.aspx + https://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates These delegates essentially generate a templated static stub function for each target function. The static function takes the first @@ -75,26 +75,42 @@ func(object, p1, p2). Pros: - * as fast as a standard function call in static and member cases - * no stub functions or double-hops needed + * as fast as a standard function call in static and non-virtual + member cases + * no stub functions needed Cons: * requires internal knowledge of the member function pointer * only works works with MSVC ABI, and not on 32-bit x86 - * does not work for classes with virtual inheritance + * does not work for classes with virtual bases * structure return does not work with member function pointers + * virtual member function lookup cannot be done in advance -------------------------------------------------------------------- Further reading: - http://www.codeproject.com/KB/cpp/FastDelegate.aspx + * http://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-pointers + Formal specification for the most common member function pointer + implementations. - http://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-pointers + * https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible + Discusses many member function pointer implementations. Based + on reverse-engineering, so not entirely accurate. In particular, + various fields are incorrectly assumed to be int-sized which is + not true in the general case. + + * https://devblogs.microsoft.com/oldnewthing/20040209-00/?p=40713 + Describes the MSVC implementation of pointers to member + functions for classes with single or multiple inheritance. Does + not mention the additional variants for virtual or unknown + inheritance. Incorrectly states that the "this" pointer + displacement is a size_t when in reality it is an int (important + for 64-bit architectures). ***************************************************************************/ -#ifndef MAME_UTIL_DELEGATE_H -#define MAME_UTIL_DELEGATE_H +#ifndef MAME_LIB_UTIL_DELEGATE_H +#define MAME_LIB_UTIL_DELEGATE_H #pragma once @@ -102,8 +118,8 @@ #include <cassert> #include <cstdint> #include <cstring> -#include <exception> #include <functional> +#include <string> #include <type_traits> #include <typeinfo> #include <utility> @@ -120,26 +136,26 @@ // select which one we will be using #if defined(MAME_DELEGATE_FORCE_COMPATIBLE) - #define USE_DELEGATE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE + #define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE #elif defined(__GNUC__) // 32bit MINGW asks for different convention #if defined(__MINGW32__) && !defined(__x86_64) && defined(__i386__) - #define USE_DELEGATE_TYPE MAME_DELEGATE_TYPE_ITANIUM + #define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_ITANIUM #define MAME_DELEGATE_MEMBER_ABI __thiscall #define MAME_DELEGATE_DIFFERENT_MEMBER_ABI 1 #elif defined(__clang__) && defined(__i386__) && defined(_WIN32) - #define USE_DELEGATE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE + #define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE #else - #define USE_DELEGATE_TYPE MAME_DELEGATE_TYPE_ITANIUM + #define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_ITANIUM #define MAME_DELEGATE_MEMBER_ABI #define MAME_DELEGATE_DIFFERENT_MEMBER_ABI 0 #endif #elif defined(_MSC_VER) && defined(_M_X64) #define MAME_DELEGATE_MEMBER_ABI #define MAME_DELEGATE_DIFFERENT_MEMBER_ABI 0 - #define USE_DELEGATE_TYPE MAME_DELEGATE_TYPE_MSVC + #define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_MSVC #else - #define USE_DELEGATE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE + #define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE #endif #if defined(__arm__) || defined(__ARMEL__) || defined(__aarch64__) @@ -152,12 +168,45 @@ #define MAME_DELEGATE_ITANIUM_ARM 0 #endif -#if USE_DELEGATE_TYPE == MAME_DELEGATE_TYPE_COMPATIBLE +#if MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_COMPATIBLE #define MAME_DELEGATE_MEMBER_ABI #define MAME_DELEGATE_DIFFERENT_MEMBER_ABI 0 #endif +/// \brief Base for objects used with late binding +/// +/// Default polymorphic class used as base for objects that can be bound +/// to after the target function has already been set. +class delegate_late_bind +{ +public: + virtual ~delegate_late_bind() = default; +}; + + +/// \brief Inappropriate late bind object error +/// +/// Thrown as an exception if the object supplied for late binding +/// cannot be cast to the target type for the delegate's function. +class binding_type_exception : public std::bad_cast +{ +public: + binding_type_exception(std::type_info const &target_type, std::type_info const &actual_type); + + virtual char const *what() const noexcept override; + + std::type_info const &target_type() const noexcept { return *m_target_type; } + std::type_info const &actual_type() const noexcept { return *m_actual_type; } + +private: + std::string m_what; + std::type_info const *m_target_type; + std::type_info const *m_actual_type; +}; + + + namespace util::detail { //************************************************************************** @@ -172,7 +221,6 @@ using delegate_generic_function = void(*)(); // define a dummy generic class that is just straight single-inheritance #ifdef _MSC_VER -class __single_inheritance generic_class; class delegate_generic_class { }; #else class delegate_generic_class; @@ -343,13 +391,13 @@ public: { return (m_function == rhs.m_function) && (m_this_delta == rhs.m_this_delta); } + bool isnull() const { -#if MAME_DELEGATE_ITANIUM_ARM - return !reinterpret_cast<void (*)()>(m_function) && !(m_this_delta & 1); -#else - return !reinterpret_cast<void (*)()>(m_function); -#endif + if (MAME_DELEGATE_ITANIUM_ARM) + return !reinterpret_cast<void (*)()>(m_function) && !(m_this_delta & 1); + else + return !reinterpret_cast<void (*)()>(m_function); } // getters @@ -446,15 +494,7 @@ public: void update_after_bind(FunctionType &funcptr, delegate_generic_class *&object) { funcptr = reinterpret_cast<FunctionType>(m_function); - std::uint8_t *byteptr = reinterpret_cast<std::uint8_t *>(object); - if ((sizeof(unknown_base_equiv) == m_size) && m_vt_index) - { - std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(byteptr + m_vptr_offs); - byteptr += *reinterpret_cast<int const *>(vptr + m_vt_index); - } - if (sizeof(single_base_equiv) < m_size) - byteptr += m_this_delta; - object = reinterpret_cast<delegate_generic_class *>(byteptr); + return adjust_this_pointer(object); } template <typename FunctionType> @@ -463,6 +503,9 @@ public: } private: + // adjust the object pointer + void adjust_this_pointer(delegate_generic_class *&object) const; + // actual state uintptr_t m_function = 0; // pointer to function or non-virtual thunk for virtual function call int m_this_delta = 0; // delta to apply to the 'this' pointer for multiple inheritance @@ -474,57 +517,75 @@ private: -#if USE_DELEGATE_TYPE == MAME_DELEGATE_TYPE_COMPATIBLE +#if MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_COMPATIBLE using delegate_mfp = delegate_mfp_compatible; -#elif USE_DELEGATE_TYPE == MAME_DELEGATE_TYPE_ITANIUM +#elif MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_ITANIUM using delegate_mfp = delegate_mfp_itanium; -#elif USE_DELEGATE_TYPE == MAME_DELEGATE_TYPE_MSVC +#elif MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_MSVC using delegate_mfp = delegate_mfp_msvc; #endif -} // namespace util::detail - -// ======================> delegate_late_bind - -// simple polymorphic class that must be mixed into any object that is late-bound -class delegate_late_bind +/// \brief Helper class for generating late bind functions +/// +/// Members of this class don't depend on the delegate's signature. +/// Keeping them here reduces the number of template instantiations as +/// you'll only need one late bind helper for each class used for late +/// binding, not for each class for each delegate signature. +template <class LateBindBase> +class delegate_late_bind_helper { public: - delegate_late_bind() { } - virtual ~delegate_late_bind() { } -}; + // make it default constructible and copyable + delegate_late_bind_helper() = default; + delegate_late_bind_helper(delegate_late_bind_helper const &) = default; + delegate_late_bind_helper(delegate_late_bind_helper &&) = default; + delegate_late_bind_helper &operator=(delegate_late_bind_helper const &) = default; + delegate_late_bind_helper &operator=(delegate_late_bind_helper &&) = default; + template <class FunctionClass> + delegate_late_bind_helper(FunctionClass *) + : m_latebinder(&delegate_late_bind_helper::late_bind_helper<FunctionClass>) + { + } -// ======================> binding_type_exception + delegate_generic_class *operator()(LateBindBase &object) { return m_latebinder(object); } -// exception that is thrown when a bind fails the dynamic_cast -class binding_type_exception : public std::exception -{ -public: - binding_type_exception(const std::type_info &target_type, const std::type_info &actual_type) - : m_target_type(target_type), m_actual_type(actual_type) - { } - const std::type_info &m_target_type; - const std::type_info &m_actual_type; + explicit operator bool() const noexcept { return bool(m_latebinder); } + +private: + using late_bind_func = delegate_generic_class*(*)(LateBindBase &object); + + template <class FunctionClass> static delegate_generic_class *late_bind_helper(LateBindBase &object); + + late_bind_func m_latebinder = nullptr; }; +template <class LateBindBase> +template <class FunctionClass> +delegate_generic_class *delegate_late_bind_helper<LateBindBase>::late_bind_helper(LateBindBase &object) +{ + FunctionClass *result = dynamic_cast<FunctionClass *>(&object); + if (result) + return reinterpret_cast<delegate_generic_class *>(result); + throw binding_type_exception(typeid(FunctionClass), typeid(object)); +} + + + //************************************************************************** // COMMON DELEGATE BASE CLASS //************************************************************************** -// ======================> delegate_base - -// general delegate class template supporting up to 5 parameters -template <typename ReturnType, typename... Params> +template <class LateBindBase, typename ReturnType, typename... Params> class delegate_base { public: // define our traits - template <class FunctionClass> using traits = util::detail::delegate_traits<FunctionClass, ReturnType, Params...>; - using generic_static_func = typename traits<util::detail::delegate_generic_class>::static_func_type; + template <class FunctionClass> using traits = delegate_traits<FunctionClass, ReturnType, Params...>; + using generic_static_func = typename traits<delegate_generic_class>::static_func_type; typedef MAME_DELEGATE_MEMBER_ABI generic_static_func generic_member_func; // generic constructor @@ -543,7 +604,7 @@ public: } // copy constructor with late bind - delegate_base(const delegate_base &src, delegate_late_bind &object) + delegate_base(const delegate_base &src, LateBindBase &object) : m_function(src.m_function) , m_latebinder(src.m_latebinder) , m_raw_function(src.m_raw_function) @@ -555,15 +616,16 @@ public: // construct from member function with object pointer template <class FunctionClass> delegate_base(typename traits<FunctionClass>::member_func_type funcptr, FunctionClass *object) - : m_latebinder(&late_bind_helper<FunctionClass>) + : m_latebinder(object) , m_raw_mfp(funcptr, object, static_cast<ReturnType *>(nullptr), static_cast<generic_static_func>(nullptr)) { bind(object); } + // construct from const member function with object pointer template <class FunctionClass> delegate_base(typename traits<FunctionClass>::const_member_func_type funcptr, FunctionClass *object) - : m_latebinder(&late_bind_helper<FunctionClass>) + : m_latebinder(object) , m_raw_mfp(funcptr, object, static_cast<ReturnType *>(nullptr), static_cast<generic_static_func>(nullptr)) { bind(object); @@ -573,7 +635,7 @@ public: template <class FunctionClass> delegate_base(typename traits<FunctionClass>::static_ref_func_type funcptr, FunctionClass *object) : m_function(reinterpret_cast<generic_static_func>(funcptr)) - , m_latebinder(&late_bind_helper<FunctionClass>) + , m_latebinder(object) , m_raw_function(reinterpret_cast<generic_static_func>(funcptr)) { bind(object); @@ -602,7 +664,6 @@ public: return (m_raw_function == rhs.m_raw_function) && (object() == rhs.object()) && (m_raw_mfp == rhs.m_raw_mfp); } - // call the function ReturnType operator()(Params... args) const { @@ -614,41 +675,25 @@ public: // getters bool has_object() const { return object() != nullptr; } - - // helpers bool isnull() const { return !m_raw_function && m_raw_mfp.isnull(); } bool is_mfp() const { return !m_raw_mfp.isnull(); } // late binding - void late_bind(delegate_late_bind &object) + void late_bind(LateBindBase &object) { if (m_latebinder) - bind((*m_latebinder)(object)); + bind(m_latebinder(object)); } protected: // return the actual object (not the one we use for calling) - util::detail::delegate_generic_class *object() const { return is_mfp() ? m_raw_mfp.real_object(m_object) : m_object; } - - // late binding function - using late_bind_func = util::detail::delegate_generic_class*(*)(delegate_late_bind &object); - - // late binding helper - template <class FunctionClass> - static util::detail::delegate_generic_class *late_bind_helper(delegate_late_bind &object) - { - FunctionClass *result = dynamic_cast<FunctionClass *>(&object); - if (!result) - throw binding_type_exception(typeid(FunctionClass), typeid(object)); - - return reinterpret_cast<util::detail::delegate_generic_class *>(result); - } + delegate_generic_class *object() const { return is_mfp() ? m_raw_mfp.real_object(m_object) : m_object; } // bind the actual object template <typename FunctionClass> void bind(FunctionClass *object) { - m_object = reinterpret_cast<util::detail::delegate_generic_class *>(object); + m_object = reinterpret_cast<delegate_generic_class *>(object); // if we're wrapping a member function pointer, handle special stuff if (m_object && is_mfp()) @@ -657,12 +702,14 @@ protected: // internal state generic_static_func m_function = nullptr; // resolved static function pointer - util::detail::delegate_generic_class * m_object = nullptr; // resolved object to the post-cast object - late_bind_func m_latebinder = nullptr; // late binding helper + delegate_generic_class * m_object = nullptr; // resolved object to the post-cast object + delegate_late_bind_helper<LateBindBase> m_latebinder; // late binding helper generic_static_func m_raw_function = nullptr; // raw static function pointer - util::detail::delegate_mfp m_raw_mfp; // raw member function pointer + delegate_mfp m_raw_mfp; // raw member function pointer }; +} // namespace util::detail + //************************************************************************** @@ -670,13 +717,13 @@ protected: //************************************************************************** // declare the base template -template <typename Signature> class delegate; +template <typename Signature, class LateBindBase = delegate_late_bind> class delegate; -template <typename ReturnType, typename... Params> -class delegate<ReturnType (Params...)> : public delegate_base<ReturnType, Params...> +template <class LateBindBase, typename ReturnType, typename... Params> +class delegate<ReturnType (Params...), LateBindBase> : public util::detail::delegate_base<LateBindBase, ReturnType, Params...> { private: - using basetype = delegate_base<ReturnType, Params...>; + using basetype = util::detail::delegate_base<LateBindBase, ReturnType, Params...>; using functoid_setter = void (*)(delegate &); template <typename T> struct functoid_type_unwrap { using type = std::remove_reference_t<T>; }; @@ -772,7 +819,7 @@ public: m_set_functoid(*this); } - delegate(delegate const &src, delegate_late_bind &object) + delegate(delegate const &src, LateBindBase &object) : basetype(src.m_functoid.has_value() ? basetype() : basetype(src, object)) , m_functoid(src.m_functoid) , m_set_functoid(src.m_set_functoid) @@ -830,4 +877,4 @@ public: } }; -#endif // MAME_UTIL_DELEGATE_H +#endif // MAME_LIB_UTIL_DELEGATE_H diff --git a/src/osd/eigccppc.h b/src/osd/eigccppc.h index de1372ff39b..b1f0fc638a7 100644 --- a/src/osd/eigccppc.h +++ b/src/osd/eigccppc.h @@ -204,7 +204,7 @@ _recip_approx(float value) multiply and return the full 128 bit result -------------------------------------------------*/ -#if defined(__ppc64__) +#if defined(__ppc64__) || defined(__PPC64___) || defined(_ARCH_PPC64) #define mul_64x64 _mul_64x64 inline int64_t ATTR_FORCE_INLINE _mul_64x64(int64_t a, int64_t b, int64_t &hi) @@ -221,7 +221,7 @@ _mul_64x64(int64_t a, int64_t b, int64_t &hi) bit multiply and return the full 128 bit result -------------------------------------------------*/ -#if defined(__ppc64__) +#if defined(__ppc64__) || defined(__PPC64___) || defined(_ARCH_PPC64) #define mulu_64x64 _mulu_64x64 inline uint64_t ATTR_FORCE_INLINE _mulu_64x64(uint64_t a, uint64_t b, uint64_t &hi) @@ -285,7 +285,7 @@ _count_leading_ones_32(uint32_t value) leading zero bits in a 64-bit value -------------------------------------------------*/ -#if defined(__ppc64__) +#if defined(__ppc64__) || defined(__PPC64___) || defined(_ARCH_PPC64) #define count_leading_zeros_64 _count_leading_zeros_64 inline uint8_t ATTR_CONST ATTR_FORCE_INLINE _count_leading_zeros_64(uint64_t value) @@ -308,7 +308,7 @@ _count_leading_zeros_64(uint64_t value) leading one bits in a 64-bit value -------------------------------------------------*/ -#if defined(__ppc64__) +#if defined(__ppc64__) || defined(__PPC64___) || defined(_ARCH_PPC64) #define count_leading_ones_64 _count_leading_ones_64 inline uint8_t ATTR_CONST ATTR_FORCE_INLINE _count_leading_ones_64(uint64_t value) |