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,38 +22,91 @@ #include -//************************************************************************** -// TYPE DEFINITIONS -//************************************************************************** +namespace { + +//------------------------------------------------- +// diamond_inheritance - forward declaration of a +// class to force MSVC to use unknown inheritance +// form of pointers to member functions +//------------------------------------------------- + +class diamond_inheritance; + + +//------------------------------------------------- +// test_delegate - a delegate that can return a +// result in a register +//------------------------------------------------- + +using test_delegate = delegate; + + +//------------------------------------------------- +// make_diamond_class_delegate - make a delegate +// bound to an instance of an incomplete class +// type +//------------------------------------------------- + +test_delegate make_diamond_class_delegate(char (diamond_inheritance::*func)(void const *&), diamond_inheritance *obj) +{ + return test_delegate(func, obj); +} + + +//------------------------------------------------- +// virtual_base - simple class that will be used +// as the top vertex of the diamond +//------------------------------------------------- + +struct virtual_base +{ + char get_base(void const *&p) { p = this; return 'x'; } + int x; +}; + + +//------------------------------------------------- +// virtual_derived_a - first class derived from +// virtual base +//------------------------------------------------- + +struct virtual_derived_a : virtual virtual_base +{ + char get_derived_a(void const *&p) { p = this; return 'a'; } + int a; +}; + + +//------------------------------------------------- +// virtual_derived_b - second class derived from +// virtual base +//------------------------------------------------- + +struct virtual_derived_b : virtual virtual_base +{ + char get_derived_b(void const *&p) { p = this; return 'b'; } + int b; +}; + + +//------------------------------------------------- +// diamond_inheritance - actual definition of +// class with diamond inheritance +//------------------------------------------------- + +class diamond_inheritance : public virtual_derived_a, public virtual_derived_b +{ +}; -//************************************************************************** -// 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) +inline char const *ioport_string_from_index(u32 index) { - return ioport_configurer::string_from_token((const char *)(uintptr_t)index); -} - - -//------------------------------------------------- -// 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; + return ioport_configurer::string_from_token(reinterpret_cast(uintptr_t(index))); } @@ -64,295 +117,19 @@ inline int validity_checker::get_defstr_index(const char *string, bool suppress_ // random_s32 //------------------------------------------------- #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); } - - - -//------------------------------------------------- -// 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(); -} +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()); } //------------------------------------------------- -// check_shared_source - check all drivers that -// share the same source file as the given driver +// validate_integer_semantics - validate that +// integers behave as expected, particularly +// with regards to overflow and shifting //------------------------------------------------- -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_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 -//------------------------------------------------- - -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_core - validate core internal systems -//------------------------------------------------- - -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::min()) || (result == std::numeric_limits::max())); - return result; - }; + auto random_i32_nolimit = + [] () + { + s32 result; + do { result = random_i32(); } while ((result == std::numeric_limits::min()) || (result == std::numeric_limits::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(); @@ -1453,6 +1232,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(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('b' != ch) + osd_printf_error("Error testing delegate non-virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing assigned delegate this pointer adjustment %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + addr = nullptr; + ch = cb2(addr); + if ('B' != ch) + osd_printf_error("Error testing delegate virtual member function dispatch\n"); + if (static_cast(&mi) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&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(&mi) != addr) + osd_printf_error("Error testing copy constructed delegate this pointer adjustment for virtual member function %p -> %p (expected %p)\n", static_cast(&mi), addr, static_cast(static_cast(&mi))); + + // test virtual member function dispatch through base pointer + cb1 = test_delegate(&base_a::get_a_v, static_cast(&o)); + cb2 = test_delegate(&base_b::get_b_v, static_cast(&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(static_cast(&o)), addr, static_cast(&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(static_cast(&o)), addr, static_cast(&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(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast(&d), addr, static_cast(static_cast(&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(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast(&d), addr, static_cast(static_cast(&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(&d) != addr) + osd_printf_error("Error testing delegate this pointer adjustment for incomplete class %p -> %p (expected %p)\n", static_cast(&d), addr, static_cast(static_cast(&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(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 cb4( + [] (auto &o, void const *&p) { p = &o; return 'l'; }, + static_cast(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; + 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(&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(&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(&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(&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(&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(&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; // 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 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 +#include //************************************************************************** // 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(reinterpret_cast(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(object), reinterpret_cast(m_function), long(m_this_delta)); + object = reinterpret_cast( + reinterpret_cast(object) + (m_this_delta >> (MAME_DELEGATE_ITANIUM_ARM ? 1 : 0))); + LOG("Calculated this=%p ", reinterpret_cast(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(m_function)); + // conventional function pointer + LOG("ptr=%p\n", reinterpret_cast(m_function)); return reinterpret_cast(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(object) + m_function; -#if MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = reinterpret_cast(uintptr_t(vtable_ptr)); -#else // MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = *reinterpret_cast(vtable_ptr); -#endif // MAME_DELEGATE_VT_DESCRIPTOR - LOG("Calculated Addr = %p (VTAB)\n", reinterpret_cast(uintptr_t(result))); + // byte index into the vtable to the function + std::uint8_t const *const vtable_ptr = *reinterpret_cast(object) + m_function - (MAME_DELEGATE_ITANIUM_ARM ? 0 : 1); + delegate_generic_function result; + if (MAME_DELEGATE_VT_DESCRIPTOR) + result = reinterpret_cast(uintptr_t(vtable_ptr)); + else + result = *reinterpret_cast(vtable_ptr); + LOG("ptr=%p (vtable)\n", reinterpret_cast(result)); return result; } -#else // MAME_DELEGATE_ITANIUM_ARM - // apply the "this" delta to the object first - object = reinterpret_cast(reinterpret_cast(object) + m_this_delta); - if (!(m_function & 1)) +} + + +//------------------------------------------------- +// 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(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(object); + if ((sizeof(unknown_base_equiv) == m_size) && m_vt_index) { - // if the low bit of the pointer is clear, then it is a conventional function pointer - LOG("Calculated Addr = %p\n", reinterpret_cast(m_function)); - return reinterpret_cast(m_function); + std::uint8_t const *const vptr = *reinterpret_cast(byteptr + m_vptr_offs); + byteptr += *reinterpret_cast(vptr + m_vt_index); } - else - { - // otherwise, it is the byte index into the vtable where the actual function lives - std::uint8_t const *const vtable_ptr = *reinterpret_cast(object) + m_function - 1; -#if MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = reinterpret_cast(uintptr_t(vtable_ptr)); -#else // MAME_DELEGATE_VT_DESCRIPTOR - delegate_generic_function const result = *reinterpret_cast(vtable_ptr); -#endif // MAME_DELEGATE_VT_DESCRIPTOR - LOG("Calculated Addr = %p (VTAB)\n", reinterpret_cast(uintptr_t(result))); - return result; - } -#endif // MAME_DELEGATE_ITANIUM_ARM + if (sizeof(single_base_equiv) < m_size) + byteptr += m_this_delta; + LOG("Calculated this=%p\n", reinterpret_cast(byteptr)); + object = reinterpret_cast(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 #include #include -#include #include +#include #include #include #include @@ -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(m_function) && !(m_this_delta & 1); -#else - return !reinterpret_cast(m_function); -#endif + if (MAME_DELEGATE_ITANIUM_ARM) + return !reinterpret_cast(m_function) && !(m_this_delta & 1); + else + return !reinterpret_cast(m_function); } // getters @@ -446,15 +494,7 @@ public: void update_after_bind(FunctionType &funcptr, delegate_generic_class *&object) { funcptr = reinterpret_cast(m_function); - std::uint8_t *byteptr = reinterpret_cast(object); - if ((sizeof(unknown_base_equiv) == m_size) && m_vt_index) - { - std::uint8_t const *const vptr = *reinterpret_cast(byteptr + m_vptr_offs); - byteptr += *reinterpret_cast(vptr + m_vt_index); - } - if (sizeof(single_base_equiv) < m_size) - byteptr += m_this_delta; - object = reinterpret_cast(byteptr); + return adjust_this_pointer(object); } template @@ -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 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 + delegate_late_bind_helper(FunctionClass *) + : m_latebinder(&delegate_late_bind_helper::late_bind_helper) + { + } + + delegate_generic_class *operator()(LateBindBase &object) { return m_latebinder(object); } + + explicit operator bool() const noexcept { return bool(m_latebinder); } + +private: + using late_bind_func = delegate_generic_class*(*)(LateBindBase &object); + + template static delegate_generic_class *late_bind_helper(LateBindBase &object); + + late_bind_func m_latebinder = nullptr; }; -// ======================> binding_type_exception - -// exception that is thrown when a bind fails the dynamic_cast -class binding_type_exception : public std::exception +template +template +delegate_generic_class *delegate_late_bind_helper::late_bind_helper(LateBindBase &object) { -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; -}; + FunctionClass *result = dynamic_cast(&object); + if (result) + return reinterpret_cast(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 +template class delegate_base { public: // define our traits - template using traits = util::detail::delegate_traits; - using generic_static_func = typename traits::static_func_type; + template using traits = delegate_traits; + using generic_static_func = typename traits::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 delegate_base(typename traits::member_func_type funcptr, FunctionClass *object) - : m_latebinder(&late_bind_helper) + : m_latebinder(object) , m_raw_mfp(funcptr, object, static_cast(nullptr), static_cast(nullptr)) { bind(object); } + // construct from const member function with object pointer template delegate_base(typename traits::const_member_func_type funcptr, FunctionClass *object) - : m_latebinder(&late_bind_helper) + : m_latebinder(object) , m_raw_mfp(funcptr, object, static_cast(nullptr), static_cast(nullptr)) { bind(object); @@ -573,7 +635,7 @@ public: template delegate_base(typename traits::static_ref_func_type funcptr, FunctionClass *object) : m_function(reinterpret_cast(funcptr)) - , m_latebinder(&late_bind_helper) + , m_latebinder(object) , m_raw_function(reinterpret_cast(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 - static util::detail::delegate_generic_class *late_bind_helper(delegate_late_bind &object) - { - FunctionClass *result = dynamic_cast(&object); - if (!result) - throw binding_type_exception(typeid(FunctionClass), typeid(object)); - - return reinterpret_cast(result); - } + delegate_generic_class *object() const { return is_mfp() ? m_raw_mfp.real_object(m_object) : m_object; } // bind the actual object template void bind(FunctionClass *object) { - m_object = reinterpret_cast(object); + m_object = reinterpret_cast(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 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 class delegate; +template class delegate; -template -class delegate : public delegate_base +template +class delegate : public util::detail::delegate_base { private: - using basetype = delegate_base; + using basetype = util::detail::delegate_base; using functoid_setter = void (*)(delegate &); template struct functoid_type_unwrap { using type = std::remove_reference_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)