summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/delegate.h
blob: 609b6667bfc5be74ad2cd99e8ba68a746a38b53a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
// license:BSD-3-Clause
// copyright-holders:Aaron Giles,Couriersud,Miodrag Milanovic,Vas Crabb
/***************************************************************************

    delegate.h

    Templates and classes to enable delegates for callbacks.

****************************************************************************

    There are many implementations of delegate-like functionality for
    C++ code, but none of them is a perfect drop-in fit for use in MAME.
    In order to be useful in MAME, we need the following properties:

        * No significant overhead; we want to use these for memory
          accessors, and memory accessor overhead is already the dominant
          performance aspect for most drivers.

        * Existing static functions need to be bound with an additional
          pointer parameter as the first argument. All existing
          implementations that allow static function binding assume the
          same signature as the member functions.

        * We must be able to bind the function separately from the
          object. This is to allow configurations to bind functions
          before the objects are created.

    Thus, the implementations below are based on existing works but are
    really a new implementation that is specific to MAME.

    --------------------------------------------------------------------

    The "compatible" version of delegates is based on an implementation
    from Sergey Ryazanov, found here:

        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
    parameter, uses it as the object pointer, and calls through the
    member function. For static functions, the stub is compatible with
    the signature of a static function, so we just set the stub directly.

    Pros:
        * should work with any modern compiler
        * static bindings are just as fast as direct calls

    Cons:
        * lots of little stub functions generated
        * double-hops on member function calls means more overhead
        * calling through stub functions repackages parameters

    --------------------------------------------------------------------

    The "Itanium" version of delegates makes use of the internal
    structure of member function pointers in order to convert them at
    binding time into simple static function pointers. This only works
    on platforms where object->func(p1, p2) is equivalent in calling
    convention to func(object, p1, p2).

    Pros:
        * as fast as a standard function call in static and member cases
        * no stub functions or double-hops needed

    Cons:
        * requires internal knowledge of the member function pointer
        * only works for two popular variants of the Itanium C++ ABI

    --------------------------------------------------------------------

    The "MSVC" version of delegates makes use of the internal structure
    of member function pointers in order to convert them at binding time
    into simple static function pointers. This only works on platforms
    where object->func(p1, p2) is equivalent in calling convention to
    func(object, p1, p2).

    Pros:
        * 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 bases
        * structure return does not work with member function pointers
        * virtual member function lookup cannot be done in advance

    --------------------------------------------------------------------

    Further reading:

        * http://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-pointers
          Formal specification for the most common member function pointer
          implementations.

        * 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_LIB_UTIL_DELEGATE_H
#define MAME_LIB_UTIL_DELEGATE_H

#pragma once

#include <any>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <utility>


//**************************************************************************
//  MACROS
//**************************************************************************

// types of delegates supported
#define MAME_DELEGATE_TYPE_COMPATIBLE 0
#define MAME_DELEGATE_TYPE_ITANIUM 1
#define MAME_DELEGATE_TYPE_MSVC 2

// select which one we will be using
#if defined(MAME_DELEGATE_FORCE_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 MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE
		//#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 MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE
	#else
		#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) || defined(_M_ARM64))
	#define MAME_DELEGATE_MEMBER_ABI
	#define MAME_DELEGATE_DIFFERENT_MEMBER_ABI 0
	#define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_MSVC
#else
	#define MAME_DELEGATE_USE_TYPE MAME_DELEGATE_TYPE_COMPATIBLE
#endif

#if defined(__arm__) || defined(__ARMEL__) || defined(__aarch64__)
	#define MAME_DELEGATE_ITANIUM_ARM 1
#elif defined(__MIPSEL__) || defined(__mips_isa_rev) || defined(__mips64)
	#define MAME_DELEGATE_ITANIUM_ARM 1
#elif defined(__EMSCRIPTEN__)
	#define MAME_DELEGATE_ITANIUM_ARM 1
#else
	#define MAME_DELEGATE_ITANIUM_ARM 0
#endif

#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 {

//**************************************************************************
//  HELPER CLASSES
//**************************************************************************

// generic function type
using delegate_generic_function = void(*)();


// ======================> generic_class

// define a dummy generic class that is just straight single-inheritance
#ifdef _MSC_VER
class delegate_generic_class { };
#else
class delegate_generic_class;
#endif


// ======================> delegate_traits

// delegate_traits is a meta-template that is used to provide a static function pointer
// and member function pointer of the appropriate type and number of parameters

template <typename ClassType, typename ReturnType, typename... Params>
struct delegate_traits
{
	using static_func_type = ReturnType (*)(ClassType *, Params...);
	using static_ref_func_type = ReturnType (*)(ClassType &, Params...);
	using member_func_type = ReturnType (ClassType::*)(Params...);
	using const_member_func_type = ReturnType (ClassType::*)(Params...) const;
};



/// \brief Maximally compatible member function pointer wrapper
///
/// Instantiates a static member function template on construction as
/// an adaptor thunk to call the supplied member function with the
/// supplied object.  Adds one layer of indirection to calls.
///
/// This implementation requires the representation of a null member
/// function pointer to be all zeroes.
class delegate_mfp_compatible
{
public:
	// default constructor
	delegate_mfp_compatible()
		: m_rawdata(s_null_mfp)
		, m_realobject(nullptr)
		, m_stubfunction(nullptr)
	{ }

	// copy constructor
	delegate_mfp_compatible(const delegate_mfp_compatible &src) = default;

	// construct from any member function pointer
	template <typename MemberFunctionType, class MemberFunctionClass, typename ReturnType, typename StaticFunctionType>
	delegate_mfp_compatible(MemberFunctionType mfp, MemberFunctionClass *, ReturnType *, StaticFunctionType)
		: m_rawdata(s_null_mfp)
		, m_realobject(nullptr)
		, m_stubfunction(make_generic<StaticFunctionType>(&delegate_mfp_compatible::method_stub<MemberFunctionClass, ReturnType>))
	{
		static_assert(sizeof(mfp) <= sizeof(m_rawdata), "Unsupported member function pointer size");
		*reinterpret_cast<MemberFunctionType *>(&m_rawdata) = mfp;
	}

	// comparison helpers
	bool operator==(const delegate_mfp_compatible &rhs) const { return m_rawdata == rhs.m_rawdata; }
	bool isnull() const { return m_rawdata == s_null_mfp; }

	// getters
	delegate_generic_class *real_object(delegate_generic_class *original) const
	{
		return m_realobject;
	}

	// binding helpers
	template <typename FunctionType>
	void update_after_bind(FunctionType &funcptr, delegate_generic_class *&object);

	template <typename FunctionType>
	void update_after_copy(FunctionType &funcptr, delegate_generic_class *&object);

private:
	// helper stubs for calling encased member function pointers
	template <class FunctionClass, typename ReturnType, typename... Params>
	static ReturnType method_stub(delegate_generic_class *object, Params ... args);

	// helper to convert a function of a given type to a generic function, forcing template
	// instantiation to match the source type
	template <typename SourceType>
	static delegate_generic_function make_generic(SourceType funcptr)
	{
		return reinterpret_cast<delegate_generic_function>(funcptr);
	}

	// FIXME: not properly aligned for storing pointers
	struct raw_mfp_data
	{
#if defined(__INTEL_COMPILER) && defined(_M_X64) // needed for "Intel(R) C++ Intel(R) 64 Compiler XE for applications running on Intel(R) 64, Version 14.0.2.176 Build 20140130" at least
		int data[((sizeof(void *) + 4 * sizeof(int)) + (sizeof(int) - 1)) / sizeof(int)];
#else // all other cases - for MSVC maximum size is one pointer, plus 3 ints; all other implementations seem to be smaller
		int data[((sizeof(void *) + 3 * sizeof(int)) + (sizeof(int) - 1)) / sizeof(int)];
#endif
		bool operator==(const raw_mfp_data &rhs) const { return !std::memcmp(data, rhs.data, sizeof(data)); }
	};

	// internal state
	raw_mfp_data                m_rawdata;          // raw buffer to hold the copy of the function pointer
	delegate_generic_class *    m_realobject;       // pointer to the object used for calling
	delegate_generic_function   m_stubfunction;     // pointer to our matching stub function

	static const raw_mfp_data   s_null_mfp;         // nullptr mfp
};


template <typename FunctionType>
void delegate_mfp_compatible::update_after_bind(FunctionType &funcptr, delegate_generic_class *&object)
{
	m_realobject = object;
	object = reinterpret_cast<delegate_generic_class *>(this);
	funcptr = reinterpret_cast<FunctionType>(m_stubfunction);
}


template <typename FunctionType>
void delegate_mfp_compatible::update_after_copy(FunctionType &funcptr, delegate_generic_class *&object)
{
	assert(reinterpret_cast<FunctionType>(m_stubfunction) == funcptr);
	object = reinterpret_cast<delegate_generic_class *>(this);
}


template <class FunctionClass, typename ReturnType, typename... Params>
ReturnType delegate_mfp_compatible::method_stub(delegate_generic_class *object, Params ... args)
{
	using mfptype = ReturnType(FunctionClass::*)(Params...);
	delegate_mfp_compatible &_this = *reinterpret_cast<delegate_mfp_compatible *>(object);
	mfptype &mfp = *reinterpret_cast<mfptype *>(&_this.m_rawdata);
	return (reinterpret_cast<FunctionClass *>(_this.m_realobject)->*mfp)(std::forward<Params>(args)...);
}



/// \brief Itanium C++ ABI member function pointer wrapper
///
/// Supports the two most popular pointer to member function
/// implementations described in the Itanium C++ ABI.  Both of these
/// consist of a pointer followed by a pointer followed by a ptrdiff_t.
///
/// The first variant is used when member the least significant bit of a
/// member function pointer need never be set and vtable entry offsets
/// are guaranteed to be even numbers of bytes.  If the pointer is even,
/// it is a conventional function pointer to the member function.  If
/// the pointer is odd, it is a byte offset into the vtable plus one.
/// The ptrdiff_t is a byte offset to add to the this pointer.  A null
/// member function pointer is represented by setting the pointer to
/// a null pointer.
///
/// The second variant is used when the least significant bit of a
/// pointer to a member function may need to be set or it may not be
/// possible to distinguish between a vtable offset and a null pointer.
/// (This is the case for ARM where the least significant bit of a
/// pointer to a function is set if the function starts in Thumb mode.)
/// If the least significant bit of the ptrdiff_t is clear, the pointer
/// is a conventional function pointer to the member function.  If the
/// least significant bit of the ptrdiff_t is set, the pointer is a byte
/// offset into the vtable.  The ptrdiff_t must be shifted right one bit
/// position to make a byte offset to add to the this pointer.  A null
/// member function pointer is represented by setting the pointer to a
/// null pointer and clearing the least significant bit of the
/// ptrdiff_t.
class delegate_mfp_itanium
{
public:
	// default constructor
	delegate_mfp_itanium() = default;

	// copy constructor
	delegate_mfp_itanium(const delegate_mfp_itanium &src) = default;

	// construct from any member function pointer
	template <typename MemberFunctionType, class MemberFunctionClass, typename ReturnType, typename StaticFunctionType>
	delegate_mfp_itanium(MemberFunctionType mfp, MemberFunctionClass *, ReturnType *, StaticFunctionType)
	{
		static_assert(sizeof(mfp) == sizeof(*this), "Unsupported member function pointer size");
		*reinterpret_cast<MemberFunctionType *>(this) = mfp;
	}

	// comparison helpers
	bool operator==(const delegate_mfp_itanium &rhs) const
	{
		return (isnull() && rhs.isnull()) || ((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);
	}

	// getters
	static delegate_generic_class *real_object(delegate_generic_class *original)
	{
		return original;
	}

	// binding helpers
	template <typename FunctionType>
	void update_after_bind(FunctionType &funcptr, delegate_generic_class *&object)
	{
		funcptr = reinterpret_cast<FunctionType>(convert_to_generic(object));
	}

	template <typename FunctionType>
	void update_after_copy(FunctionType &funcptr, delegate_generic_class *&object)
	{
	}

private:
	// extract the generic function and adjust the object pointer
	delegate_generic_function convert_to_generic(delegate_generic_class *&object) const;

	// actual state
	uintptr_t   m_function = reinterpret_cast<uintptr_t>(static_cast<void (*)()>(nullptr)); // function pointer or vtable offset
	ptrdiff_t   m_this_delta = 0;                                                           // delta to apply to the 'this' pointer
};



/// \brief MSVC member function pointer wrapper
///
/// MSVC uses space optimisation.  A member function pointer is a
/// conventional function pointer followed by zero to three int values,
/// depending on whether the class has single, multiple, virtual or
/// unknown inheritance of base classes.  The function pointer is always
/// a conventional function pointer (a thunk is used to call virtual
/// member functions through the vtable).
///
/// If present, the first int value is a byte offset to add to the this
/// pointer before calling the function.
///
/// For the virtual inheritance case, the offset to the vtable pointer
/// from the location the this pointer points to must be known by the
/// compiler when the member function pointer is called.  The second int
/// value is a byte offset into the vtable to an int value containing an
/// additional byte offset to add to the this pointer.
///
/// For the unknown inheritance case, the second int value is a byte
/// offset add to the this pointer to obtain a pointer to the vtable
/// pointer, or undefined if not required.  If the third int value is
/// not zero, it is a byte offset into the vtable to an int value
/// containing an additional byte offset to add to the this pointer.
///
/// It is not possible to support the virtual inheritance case without
/// some way of obtaining the offset to the vtable pointer.
class delegate_mfp_msvc
{
	struct single_base_equiv { delegate_generic_function fptr; };
	struct multi_base_equiv { delegate_generic_function fptr; int thisdisp; };
	struct unknown_base_equiv { delegate_generic_function fptr; int thisdisp, vptrdisp, vtdisp; };

public:
	// default constructor
	delegate_mfp_msvc() = default;

	// copy constructor
	delegate_mfp_msvc(const delegate_mfp_msvc &src) = default;

	// construct from any member function pointer
	template <typename MemberFunctionType, class MemberFunctionClass, typename ReturnType, typename StaticFunctionType>
	delegate_mfp_msvc(MemberFunctionType mfp, MemberFunctionClass *, ReturnType *, StaticFunctionType)
	{
		// FIXME: this doesn't actually catch the unsupported virtual inheritance case on 64-bit targets
		// alignment of the pointer means sizeof gives the same value for multiple inheritance and virtual inheritance cases
		static_assert(
				(sizeof(mfp) == sizeof(single_base_equiv)) || (sizeof(mfp) == sizeof(multi_base_equiv)) || (sizeof(mfp) == sizeof(unknown_base_equiv)),
				"Unsupported member function pointer size");
		static_assert(sizeof(mfp) <= sizeof(*this), "Member function pointer is too large to support");
		*reinterpret_cast<MemberFunctionType *>(this) = mfp;
		m_size = sizeof(mfp);
	}

	// comparison helpers
	bool operator==(const delegate_mfp_msvc &rhs) const
	{
		if (m_function != rhs.m_function)
		{
			return false;
		}
		else if (sizeof(single_base_equiv) == m_size)
		{
			return (sizeof(single_base_equiv) == rhs.m_size) || (!rhs.m_this_delta && ((sizeof(multi_base_equiv) == rhs.m_size) || !rhs.m_vt_index));
		}
		else if (sizeof(multi_base_equiv) == m_size)
		{
			if (sizeof(unknown_base_equiv) == rhs.m_size)
				return (m_this_delta == rhs.m_this_delta) && !rhs.m_vt_index;
			else
				return (sizeof(single_base_equiv) == rhs.m_size) ? !m_this_delta : (m_this_delta == rhs.m_this_delta);
		}
		else if (sizeof(unknown_base_equiv) == rhs.m_size)
		{
			return (m_this_delta == rhs.m_this_delta) && (m_vt_index == rhs.m_vt_index) && (!m_vt_index || (m_vptr_offs == rhs.m_vptr_offs));
		}
		else
		{
			return !m_vt_index && ((sizeof(multi_base_equiv) == rhs.m_size) ? (m_this_delta == rhs.m_this_delta) : !m_this_delta);
		}
	}

	bool isnull() const
	{
		return !reinterpret_cast<void (*)()>(m_function);
	}

	// getters
	static delegate_generic_class *real_object(delegate_generic_class *original) { return original; }

	// binding helpers
	template <typename FunctionType>
	void update_after_bind(FunctionType &funcptr, delegate_generic_class *&object)
	{
		funcptr = reinterpret_cast<FunctionType>(adjust_this_pointer(object));
	}

	template <typename FunctionType>
	void update_after_copy(FunctionType &funcptr, delegate_generic_class *&object)
	{
	}

private:
	// adjust the object pointer and bypass thunks
	delegate_generic_function 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
	int         m_vptr_offs = 0;    // offset to apply to this pointer to obtain pointer to vptr
	int         m_vt_index = 0;     // offset into vtable to additional delta to apply to the 'this' pointer

	unsigned    m_size = 0;         // overall size of the pointer to member function representation
};



#if MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_COMPATIBLE

template <typename ReturnType>
struct delegate_mfp { using type = delegate_mfp_compatible; };

#elif MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_ITANIUM

template <typename ReturnType>
struct delegate_mfp { using type = delegate_mfp_itanium; };

#elif MAME_DELEGATE_USE_TYPE == MAME_DELEGATE_TYPE_MSVC

/// \brief Determine whether a type is returned conventionally
///
/// Under the MSVC C++ ABI with the Microsoft calling convention for
/// x86-64 or AArch64, the calling convention for member functions is
/// not quite the same as a free function with the "this" pointer as the
/// first parameter.
///
/// Conventionally, structure and union values can be returned in
/// registers if they are small enough and are aggregates (trivially
/// constructible, destructible, copyable and assignable).  On x86-64,
/// if the value cannot be returned in registers, the pointer to the
/// area for the return value is conventionally passed in RCX and
/// explicit parameters are shifted by one position.  On AArch64, if the
/// value cannot be returned in registers, the pointer to the area for
/// the return value is passed in X8 (explicit parameters do not need to
/// be shifted).
///
/// For member functions, structure and union types are never returned
/// in registers, and the pointer to the area for the return value is
/// passed differently for structures and unions.  When a structure or
/// union is to be returned, a pointer to the area for the return value
/// is effectively passed as a second implicit parameter.  On x86-64,
/// the "this" pointer is passed in RCX and the pointer to the area for
/// the return value is passed in RDX; on AArch64, the "this" pointer is
/// passed in X0 and the pointer to the area for the return value is
/// passed in X1.  Explicit parameters are shifted an additional
/// position to allow for the second implicit parameter.
///
/// Note that pointer types are returned conventionally from member
/// functions even when they're too large to return in registers (e.g. a
/// pointer to a function member of a class with unknown inheritance).
///
/// Because of this, we may need to use the #delegate_mfp_compatible
/// class to generate adaptor thunks depending on the return type.  This
/// trait doesn't need to reliably be true for types that are returned
/// conventionally from member functions; it only needs to reliably be
/// false for types that aren't.  Incorrectly yielding true will result
/// in incorrect behaviour while incorrectly yielding false will just
/// cause increased overhead (both compile-time and run-time).
template <typename ReturnType>
using delegate_mfp_conventional_return = std::bool_constant<
		std::is_void_v<ReturnType> ||
		std::is_scalar_v<ReturnType> ||
		std::is_reference_v<ReturnType> >;

template <typename ReturnType, typename Enable = void>
struct delegate_mfp;

template <typename ReturnType>
struct delegate_mfp<ReturnType, std::enable_if_t<delegate_mfp_conventional_return<ReturnType>::value> > { using type = delegate_mfp_msvc; };

template <typename ReturnType>
struct delegate_mfp<ReturnType, std::enable_if_t<!delegate_mfp_conventional_return<ReturnType>::value> > { using type = delegate_mfp_compatible; };

#endif

template <typename ReturnType> using delegate_mfp_t = typename delegate_mfp<ReturnType>::type;



/// \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:
	// 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>)
	{
	}

	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 <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
//**************************************************************************

template <class LateBindBase, typename ReturnType, typename... Params>
class delegate_base
{
public:
	// define our traits
	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
	delegate_base() = default;

	// copy constructor
	delegate_base(const delegate_base &src)
		: m_function(src.m_function)
		, m_object(src.m_object)
		, m_latebinder(src.m_latebinder)
		, m_raw_function(src.m_raw_function)
		, m_raw_mfp(src.m_raw_mfp)
	{
		if (src.object() && is_mfp())
			m_raw_mfp.update_after_copy(m_function, m_object);
	}

	// copy constructor with late bind
	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)
		, m_raw_mfp(src.m_raw_mfp)
	{
		late_bind(object);
	}

	// construct from member function with object pointer
	template <class FunctionClass>
	delegate_base(typename traits<FunctionClass>::member_func_type funcptr, FunctionClass *object)
		: 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(object)
		, m_raw_mfp(funcptr, object, static_cast<ReturnType *>(nullptr), static_cast<generic_static_func>(nullptr))
	{
		bind(object);
	}

	// construct from static reference function with object reference
	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(object)
		, m_raw_function(reinterpret_cast<generic_static_func>(funcptr))
	{
		bind(object);
	}

	// copy operator
	delegate_base &operator=(const delegate_base &src)
	{
		if (this != &src)
		{
			m_function = src.m_function;
			m_object = src.m_object;
			m_latebinder = src.m_latebinder;
			m_raw_function = src.m_raw_function;
			m_raw_mfp = src.m_raw_mfp;

			if (src.object() && is_mfp())
				m_raw_mfp.update_after_copy(m_function, m_object);
		}
		return *this;
	}

	// comparison helper
	bool operator==(const delegate_base &rhs) const
	{
		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
	{
		if ((MAME_DELEGATE_DIFFERENT_MEMBER_ABI) && is_mfp())
			return (*reinterpret_cast<generic_member_func>(m_function))(m_object, std::forward<Params>(args)...);
		else
			return (*m_function)(m_object, std::forward<Params>(args)...);
	}

	// getters
	bool has_object() const { return object() != nullptr; }
	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(LateBindBase &object)
	{
		if (m_latebinder)
			bind(m_latebinder(object));
	}

protected:
	// return the actual object (not the one we use for calling)
	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<delegate_generic_class *>(object);

		// if we're wrapping a member function pointer, handle special stuff
		if (m_object && is_mfp())
			m_raw_mfp.update_after_bind(m_function, m_object);
	}

	// internal state
	generic_static_func                     m_function = nullptr;       // resolved static function pointer
	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
	delegate_mfp_t<ReturnType>              m_raw_mfp;                  // raw member function pointer
};

} // namespace util::detail



//**************************************************************************
//  NATURAL SYNTAX
//**************************************************************************

// declare the base template
template <typename Signature, class LateBindBase = delegate_late_bind> class delegate;

template <class LateBindBase, typename ReturnType, typename... Params>
class delegate<ReturnType (Params...), LateBindBase> : public util::detail::delegate_base<LateBindBase, ReturnType, Params...>
{
private:
	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>; };
	template <typename T> struct functoid_type_unwrap<std::reference_wrapper<T> > { using type = typename functoid_type_unwrap<T>::type; };
	template <typename T> using unwrapped_functoid_t = typename functoid_type_unwrap<std::remove_cv_t<std::remove_reference_t<T> > >::type;

	template <typename T> static constexpr bool matching_non_const_call(T &&) { return false; }
	template <typename T> static constexpr bool matching_non_const_call(ReturnType (T::*)(Params...)) { return true; }
	template <typename T> static constexpr bool matching_const_call(T &&) { return false; }
	template <typename T> static constexpr bool matching_const_call(ReturnType (T::*)(Params...) const) { return true; }

	template <typename T> static T *unwrap_functoid(T *functoid) { return functoid; }
	template <typename T> static T *unwrap_functoid(std::reference_wrapper<T> *functoid) { return &functoid->get(); }

	template <typename T>
	unwrapped_functoid_t<T> *unwrap_functoid() noexcept
	{
		return unwrap_functoid(std::any_cast<std::remove_cv_t<std::remove_reference_t<T> > >(&m_functoid));
	}

	template <typename T>
	static functoid_setter make_functoid_setter()
	{
		if constexpr (matching_non_const_call(&unwrapped_functoid_t<T>::operator()))
		{
			return
					[] (delegate &obj)
					{
						obj.basetype::operator=(
								basetype(
									static_cast<ReturnType (unwrapped_functoid_t<T>::*)(Params...)>(&unwrapped_functoid_t<T>::operator()),
									obj.unwrap_functoid<T>()));
					};
		}
		else if constexpr (matching_const_call(&unwrapped_functoid_t<T>::operator()))
		{
			return
					[] (delegate &obj)
					{
						obj.basetype::operator=(
								basetype(
									static_cast<ReturnType (unwrapped_functoid_t<T>::*)(Params...) const>(&unwrapped_functoid_t<T>::operator()),
									obj.unwrap_functoid<T>()));
					};
		}
		else
		{
			return
					[] (delegate &obj)
					{
						obj.basetype::operator=(
								basetype(
									[] (unwrapped_functoid_t<T> &f, Params... args) { return ReturnType(f(std::forward<Params>(args)...)); },
									obj.unwrap_functoid<T>()));
					};
		}
	}

	std::any m_functoid;
	functoid_setter m_set_functoid = nullptr;

protected:
	template <class FunctionClass> using traits = typename basetype::template traits<FunctionClass>;
	template <class FunctionClass> using member_func_type = typename traits<FunctionClass>::member_func_type;
	template <class FunctionClass> using const_member_func_type = typename traits<FunctionClass>::const_member_func_type;
	template <class FunctionClass> using static_ref_func_type = typename traits<FunctionClass>::static_ref_func_type;

	template <typename T> using suitable_functoid = std::is_invocable_r<ReturnType, T, Params...>;

public:
	delegate() : basetype() { }

	delegate(delegate const &src)
		: basetype(src.m_functoid.has_value() ? static_cast<const basetype &>(basetype()) : src)
		, m_functoid(src.m_functoid)
		, m_set_functoid(src.m_set_functoid)
	{
		if (m_functoid.has_value())
			m_set_functoid(*this);
	}

	delegate(delegate &src)
		: delegate(const_cast<delegate const &>(src))
	{
	}

	delegate(delegate &&src)
		: basetype(src.m_functoid.has_value() ? basetype() : std::move(src))
		, m_functoid(std::move(src.m_functoid))
		, m_set_functoid(std::move(src.m_set_functoid))
	{
		if (m_functoid.has_value())
			m_set_functoid(*this);
	}

	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)
	{
		if (m_functoid.has_value())
			m_set_functoid(*this);
	}

	template <class FunctionClass> delegate(member_func_type<FunctionClass> funcptr, FunctionClass *object) : basetype(funcptr, object) { }
	template <class FunctionClass> delegate(const_member_func_type<FunctionClass> funcptr, FunctionClass *object) : basetype(funcptr, object) { }
	template <class FunctionClass> delegate(static_ref_func_type<FunctionClass> funcptr, FunctionClass *object) : basetype(funcptr, object) { }

	template <typename T>
	explicit delegate(T &&functoid, std::enable_if_t<suitable_functoid<T>::value, int> = 0)
		: basetype()
		, m_functoid(std::forward<T>(functoid))
		, m_set_functoid(make_functoid_setter<T>())
	{
		m_set_functoid(*this);
	}

	delegate &operator=(std::nullptr_t) noexcept
	{
		reset();
		return *this;
	}

	delegate &operator=(delegate const &src)
	{
		m_functoid = src.m_functoid;
		m_set_functoid = src.m_set_functoid;
		if (m_functoid.has_value())
			m_set_functoid(*this);
		else
			basetype::operator=(src);
		return *this;
	}

	delegate &operator=(delegate &&src)
	{
		m_functoid = std::move(src.m_functoid);
		m_set_functoid = std::move(src.m_set_functoid);
		if (m_functoid.has_value())
			m_set_functoid(*this);
		else
			basetype::operator=(std::move(src));
		return *this;
	}

	void reset() noexcept
	{
		basetype::operator=(basetype());
		m_functoid.reset();
		m_set_functoid = nullptr;
	}
};

#endif // MAME_LIB_UTIL_DELEGATE_H