1 // Written in the D programming language.
2 
3 /++
4  This module implements fast open multi-_methods.
5 
6  Open _methods are like virtual functions, except that they are free functions,
7  living outside of any class. Multi-_methods can take into account the dynamic
8  types of more than one argument to select the most specialized variant of the
9  function.
10 
11  This implementation uses compressed dispatch tables to deliver a performance
12  similar to ordinary virtual function calls, while minimizing the size of the
13  dispatch tables in the presence of multiple virtual arguments.
14 
15  Synopsis of openmethods:
16 ---
17 
18 import openmethods; // import lib
19 mixin(registerMethods); // mixin must be called after importing module
20 
21 interface  Animal {}
22 class Dog : Animal {}
23 class Pitbull : Dog {}
24 class Cat : Animal {}
25 class Dolphin : Animal {}
26 
27 // open method with single argument <=> virtual function "from outside"
28 string kick(virtual!Animal);
29 
30 @method // implement 'kick' for dogs
31 string _kick(Dog x) // note the underscore
32 {
33   return "bark";
34 }
35 
36 @method("kick") // use a different name for specialization
37 string notGoodIdea(Pitbull x)
38 {
39   return next!kick(x) ~ " and bite"; // aka call 'super'
40 }
41 
42 // multi-method
43 string meet(virtual!Animal, virtual!Animal);
44 
45 // 'meet' implementations
46 @method
47 string _meet(Animal, Animal)
48 {
49   return "ignore";
50 }
51 
52 @method
53 string _meet(Dog, Dog)
54 {
55   return "wag tail";
56 }
57 
58 @method
59 string _meet(Dog, Cat)
60 {
61   return "chase";
62 }
63 
64 void main()
65 {
66   updateMethods(); // once per process - don't forget!
67 
68   import std.stdio;
69 
70   Animal hector = new Pitbull, snoopy = new Dog;
71   writeln("kick snoopy: ", kick(snoopy)); // bark
72   writeln("kick hector: ", kick(hector)); // bark and bite
73 
74   Animal felix = new Cat, flipper = new Dolphin;
75   writeln("hector meets felix: ", meet(hector, felix)); // chase
76   writeln("hector meets snoopy: ", meet(hector, snoopy)); // wag tail
77   writeln("hector meets flipper: ", meet(hector, flipper)); // ignore
78 }
79 ---
80 
81  Copyright: Copyright Jean-Louis Leroy 2017
82 
83  License:   $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0).
84 
85  Authors:   Jean-Louis Leroy 2017
86 +/
87 
88 module openmethods;
89 
90 import std.algorithm;
91 import std.bitmanip;
92 import std.exception;
93 import std.format;
94 import std.meta;
95 import std.range;
96 import std.traits;
97 
98 version (GNU) {
99   import std.datetime;
100 } else {
101   import std.datetime.stopwatch;
102 }
103 
104 debug(explain) {
105   import std.stdio;
106 }
107 
108 debug(traceCalls) {
109   import std.stdio;
110 }
111 
112 // ============================================================================
113 // Public stuff
114 
115 /++
116  Mark a parameter as virtual, and declare a method.
117 
118  A new function is introduced in the current scope. It has the same name as the
119  declared method; its parameter list consists of the declared parameters,
120  stripped from the `virtual!` qualifier. Calls to this function resolve to the
121  most specific method that matches the arguments.
122 
123  The rules for determining the most specific function are exactly the same as
124  those that guide the resolution of function calls in presence of overloads -
125  only the resolution happens at run time, taking into account the argument's
126  $(I dynamic) type. In contrast, the normal function overload resolution is a
127  compile time mechanism that takes into account the $(I static) type of the
128  arguments.
129 
130  Examples:
131  ---
132  Matrix times(double, virtual!Matrix);
133  Matrix a = new DiagonalMatrix(...);
134  auto result = times(2, a);
135 
136  string fight(virtual!Character, virtual!Creature, virtual!Device);
137  fight(player, room.guardian, bag[item]);
138  ---
139  +/
140 
141 struct virtual(T)
142 {
143 }
144 
145 /++
146  Mark a parameter as covariant.
147 
148  Marking a parameter as covariant makes it possible to override a method with
149  an override that has a type-compatible parameter in the same position.
150  Covariant parameters are not taken into account for override selection. The
151  arguments passed in covariant parameters are automatically cast to the types
152  required by the override.
153 
154  `covariant` is useful when it is known for certain that the overrides will
155  always be called with arguments of the required type. This can help improve
156  performance and reduce the size of method dispatch tables.
157 
158  Examples:
159  ---
160  class Container {}
161  class Bottle : Container {}
162  class Can : Container {}
163  class Tool {}
164  class Corkscrew : Tool {}
165  class CanOpener : Tool {}
166 
167  void open(virtual!Container, covariant!Tool);
168  @method void _open(Bottle bottle, Corkscrew corkscrew) {} // 1
169  @method void _open(Can can, CanOpener opener) {}          // 2
170  // ...
171 
172  Container container = new Bottle();
173  Tool tool = new Corkscrew();
174  open(container, tool);
175  // override #1 is selected based solely on first argument's type
176  // second argument is cast to Corkscrew
177  // The following is illegal:
178  Tool wrongTool = new CanOpener();
179  open(container, wrongTool);
180  // override #1 is called but is passed a CanOpener.
181  ---
182  +/
183 
184 struct covariant(T)
185 {
186 }
187 
188 /++
189  Attribute: Set the policy for storing and retrieving the method pointer (mptr).
190 
191  Each class involved in method dispatch (either because it occurs as a virtual
192  parameter, or is derived from a class or an interface that occurs as a virtual
193  parameter) has an associated mptr. The first step of method dispatch consists
194  in retrieving the mptr for each virtual argument.
195 
196  Two policies are supported: "deallocator": store the mptr in the deprecated
197  `deallocator` field of ClassInfo. This is the default, and delivers the best
198  performance. $(NOTE:) This policy is incompatible with classes that implement
199  the deprecated `operator delete`.
200 
201  "hash": store the mptr in a hash table. The mptr is obtained by
202  applying a perfect hash function to the class' vptr. This policy is only
203  slightly slower than the deallocator policy.
204 
205  Example:
206  ---
207  @mptr("hash")
208  string fight(virtual!Character, virtual!Creature, virtual!Device);
209  ---
210  +/
211 
212 struct mptr
213 {
214   string index;
215 }
216 
217 /++
218  Attribute: Add an override to a method.
219 
220  If called without an argument, the function name must consist of a method
221  name, prefixed with an underscore. The function is added to the method as a
222  specialization.
223 
224  If called with a string argument, the string indicates the name of the method
225  to specialize. The function name can then be any valid identifier. This is
226  useful to allow an override to call a specific override without going through
227  the dynamic dispatch mechanism.
228 
229  Examples:
230  ---
231  @method
232  string _fight(Character x, Creature y, Axe z)
233  {
234    ...
235  }
236 
237  @method("times")
238  Matrix doubleTimesDiagonal(double a, immutable(DiagonalMatrix) b)
239  {
240    ...
241  }
242  ---
243 
244 +/
245 
246 struct method
247 {
248   string id;
249 }
250 
251 /++ Call the _next most specialized override, if it exists. In other words, call
252  the override that would have been called if this one had not been defined.
253 
254  Examples:
255  ---
256 void inspect(virtual!Vehicle, virtual!Inspector);
257 
258 @method
259 void _inspect(Vehicle v, Inspector i)
260 {
261   writeln("Inspect vehicle.");
262 }
263 
264 @method
265 void _inspect(Car v, Inspector i)
266 {
267   next!inspect(v, i);
268   writeln("Inspect seat belts.");
269 }
270 
271 @method
272 void _inspect(Car v, StateInspector i)
273 {
274   next!inspect(v, i);
275   writeln("Check insurance.");
276 }
277 
278 ...
279 
280 Vehicle car = new Car;
281 Inspector inspector = new StateInspector;
282 inspect(car, inspector); // Inspect vehicle. Inspect seat belts. Check insurance.
283  ---
284 +/
285 
286 auto next(alias F, T...)(T args)
287 {
288   alias M = typeof(F(MethodTag.init, T.init));
289   return M.nextPtr!(T)(args);
290 }
291 
292 /++ Used as a string mixin: register the method declarations and definitions in
293  the current module.
294 
295  Examples:
296  ---
297 import openmethods;
298 mixin(registerMethods);
299  ---
300  +/
301 
302 auto registerMethods(string moduleName = __MODULE__)
303 {
304   return `static import openmethods;
305 import std.traits : FunctionAttribute;
306 mixin(openmethods._registerMethods!(%s));
307 mixin openmethods._registerSpecs!(%s);`.format(moduleName, moduleName);
308 }
309 
310 mixin template declareMethod(string index, ReturnType, string name, ParameterType...)
311 {
312   mixin(openmethods.Method!(index, ReturnType, name,
313                             FunctionAttribute.none, ParameterType)
314         .code);
315 }
316 
317 mixin template declareMethod(ReturnType, string name, ParameterType...)
318 {
319   import std.traits;
320   mixin(openmethods.Method!(openmethods.MptrInDeallocator, ReturnType, name,
321                             FunctionAttribute.none, ParameterType)
322         .code);
323 }
324 
325 mixin template defineMethod(alias Dispatcher, alias Fun)
326 {
327   static struct RegisterMethod {
328 
329     import openmethods;
330     import std.traits;
331 
332     alias Meth = typeof(Dispatcher(MethodTag.init, Parameters!(Fun).init));
333 
334     static wrapper = function Meth.ReturnType(Meth.Params args) {
335       return Fun(openmethods.castArgs!(Meth.QualParams).To!(Parameters!Fun).arglist(args).expand);
336     };
337 
338     static __gshared Runtime.SpecInfo si;
339 
340     shared static this() {
341       si.pf = cast(void*) wrapper;
342 
343       debug(explain) {
344         import std.stdio;
345         writefln("Registering override %s%s", Meth.name, Parameters!Fun.stringof);
346       }
347 
348       foreach (i, QP; Meth.QualParams) {
349         static if (IsVirtual!QP) {
350           si.vp ~= Parameters!Fun[i].classinfo;
351         }
352       }
353 
354       Meth.info.specInfos ~= &si;
355       si.nextPtr = cast(void**) &Meth.nextPtr!(Parameters!Fun);
356 
357       Runtime.needUpdate = true;
358     }
359 
360     shared static ~this()
361     {
362       debug(explain) {
363         import std.stdio;
364         writefln("Removing override %s%s", Meth.name, Parameters!Fun.stringof);
365       }
366 
367       import std.algorithm, std.array;
368       Meth.info.specInfos = Meth.info.specInfos.filter!(p => p != &si).array;
369       Runtime.needUpdate = true;
370     }
371   }
372 
373   __gshared RegisterMethod registerMethod;
374 }
375 
376 mixin template registerClasses(Classes...) {
377   shared static this() {
378     foreach (C; Classes) {
379       debug(explain) {
380         import std.stdio;
381         writefln("Registering class %s", C.stringof);
382       }
383       Runtime.additionalClasses ~= C.classinfo;
384     }
385   }
386 
387   shared static ~this()
388   {
389     foreach (C; Classes) {
390       debug(explain) {
391         import std.stdio;
392         writefln("Unregistering class %s", C.stringof);
393       }
394       import std.algorithm, std.array;
395       Runtime.additionalClasses =
396         Runtime.additionalClasses.filter!(c => c != C.classinfo).array;
397       Runtime.needUpdate = true;
398     }
399   }
400 }
401 
402 /++
403  Update the runtime dispatch tables. Must be called once before calling any
404  methods. Typically this is done at the beginning of `main`.
405  +/
406 
407 Runtime.Metrics updateMethods()
408 {
409   Runtime rt;
410   return rt.update();
411 }
412 
413 bool needUpdateMethods()
414 {
415   return Runtime.needUpdate;
416 }
417 
418 /++
419  Information passed to the error handler function.
420 
421  +/
422 
423 class MethodError : Error
424 {
425   this(int reason, const(Runtime.MethodInfo)* meth) {
426     super(reason.stringof);
427     this.reason = reason;
428     this.meth = meth;
429   }
430 
431   @property string functionName() { return meth.name; }
432 
433   enum NotImplemented = 1, AmbiguousCall = 2, DeallocatorInUse = 3;
434   const Runtime.MethodInfo* meth;
435   int reason;
436   TypeInfo[] args;
437 }
438 
439 void defaultMethodErrorHandler(MethodError error)
440 {
441   import std.stdio;
442   stderr.writefln("call to %s(%s) is %s, aborting...",
443                   error.functionName,
444                   error.args.map!(a => a.toString).join(", "),
445                   error.reason == MethodError.NotImplemented
446                   ? "not implemented" : "ambiguous");
447   import core.stdc.stdlib : abort;
448   abort();
449 }
450 
451 alias MethodErrorHandler = void function(MethodError error);
452 
453 MethodErrorHandler errorHandler = &defaultMethodErrorHandler;
454 
455 /++
456  Set the error handling function to be called if an open method cannot be
457  called with the provided arguments. The default is to `abort` the program.
458 +/
459 
460 void function(MethodError error)
461 setMethodErrorHandler(void function(MethodError error) handler)
462 {
463   auto prev = errorHandler;
464   errorHandler = handler;
465   return prev;
466 }
467 
468 // ============================================================================
469 // Private parts. This doesn't exist. If you believe it does and use it, on
470 // your head be it.
471 
472 enum IsVirtual(T) = false;
473 enum IsVirtual(T : virtual!U, U) = true;
474 
475 alias VirtualType(T : virtual!U, U) = U;
476 
477 template VirtualArity(QP...)
478 {
479   static if (QP.length == 0) {
480     enum VirtualArity = 0;
481   } else static if (IsVirtual!(QP[0])) {
482     enum VirtualArity = 1 + VirtualArity!(QP[1..$]);
483   } else {
484     enum VirtualArity = VirtualArity!(QP[1..$]);
485   }
486 }
487 
488 enum IsCovariant(T) = false;
489 enum IsCovariant(T : covariant!U, U) = true;
490 
491 private alias CovariantType(T : covariant!U, U) = U;
492 
493 private template CallParams(T...)
494 {
495   static if (T.length == 0) {
496     alias CallParams = AliasSeq!();
497   } else {
498     static if (IsVirtual!(T[0])) {
499       alias CallParams = AliasSeq!(VirtualType!(T[0]), CallParams!(T[1..$]));
500     } else static if (IsCovariant!(T[0])) {
501       alias CallParams = AliasSeq!(CovariantType!(T[0]), CallParams!(T[1..$]));
502     } else {
503       alias CallParams = AliasSeq!(T[0], CallParams!(T[1..$]));
504     }
505   }
506 }
507 
508 template castArgs(T...)
509 {
510   import std.typecons : tuple;
511   static if (T.length) {
512     template To(S...)
513     {
514       auto arglist(A...)(A args) {
515         alias QP = T[0];
516         static if (IsVirtual!QP) {
517           static if (is(VirtualType!QP == class)) {
518             auto arg = cast(S[0]) cast(void*) args[0];
519           } else {
520             static assert(is(VirtualType!QP == interface),
521                              "virtual argument must be a class or an interface");
522             auto arg = cast(S[0]) args[0];
523           }
524         } else static if (IsCovariant!QP) {
525           static if (is(CovariantType!QP == class)) {
526             debug {
527               auto arg = cast(S[0]) args[0];
528             } else {
529               auto arg = cast(S[0]) cast(void*) args[0];
530             }
531           } else {
532             static assert(is(CovariantType!QP == interface),
533                              "covariant argument must be a class or an interface");
534             auto arg = cast(S[0]) args[0];
535           }
536         } else {
537           auto arg = args[0];
538         }
539         return
540           tuple(arg,
541                 castArgs!(T[1..$]).To!(S[1..$]).arglist(args[1..$]).expand);
542       }
543     }
544   } else {
545     template To(X...)
546     {
547       auto arglist() {
548         return tuple();
549       }
550     }
551   }
552 }
553 
554 immutable MptrInDeallocator = "deallocator";
555 immutable MptrViaHash = "hash";
556 
557 struct Method(string Mptr, R, string id, FunctionAttribute functionAttributes_, T...)
558 {
559   alias QualParams = T;
560   alias Params = CallParams!T;
561   alias ReturnType = R;
562   alias Word =  Runtime.Word;
563   enum name = id;
564   alias functionAttributes = functionAttributes_;
565   alias This = Method!(Mptr, R, id, functionAttributes, T);
566 
567 
568   static if (functionAttributes & FunctionAttribute.ref_) {
569     alias ref R function(Params) Spec;
570   } else {
571     alias R function(Params) Spec;
572   }
573 
574   static __gshared Runtime.MethodInfo info;
575 
576   static R notImplementedError(T...)
577   {
578     import std.meta;
579     errorHandler(new MethodError(MethodError.NotImplemented, &info));
580     static if (!is(R == void)) {
581       return R.init;
582     }
583   }
584 
585   static R ambiguousCallError(T...)
586   {
587     errorHandler(new MethodError(MethodError.AmbiguousCall, &info));
588     static if (!is(R == void)) {
589       return R.init;
590     }
591   }
592 
593   static Method discriminator(MethodTag, CallParams!T);
594 
595   static if (Mptr == MptrInDeallocator) {
596     static auto getMptr(T)(T arg)
597     {
598       alias Word = Runtime.Word;
599       static if (is(T == class)) {
600         return cast(const Word*) arg.classinfo.deallocator;
601       } else {
602         Object o = cast(Object)
603           (cast(void*) arg - (cast(Interface*) **cast(void***) arg).offset);
604         return cast(const Word*) o.classinfo.deallocator;
605       }
606     }
607   } else static if (Mptr == MptrViaHash) {
608     static auto getMptr(T)(T arg) {
609       alias Word = Runtime.Word;
610       static if (is(T == class)) {
611         return Runtime.gv.ptr[Runtime.hash(*cast (void**) arg)].pw;
612       } else {
613         Object o = cast(Object)
614           (cast(void*) arg - (cast(Interface*) **cast(void***) arg).offset);
615         return Runtime.gv.ptr[Runtime.hash(*cast (void**) o)].pw;
616       }
617     }
618   }
619 
620   template Indexer(Q...)
621   {
622     static const(Word)* move(P...)(const(Word)* slotStride, P args)
623     {
624       alias Q0 = Q[0];
625       debug(traceCalls) {
626         stderr.write("\n  ", Q0.stringof, ":");
627       }
628       static if (IsVirtual!Q0) {
629         alias arg = args[0];
630         const (Word)* mtbl = getMptr(arg);
631         auto slot = slotStride++.i;
632         auto index = mtbl[slot].i;
633         debug(traceCalls) {
634           stderr.writef(" mtbl = %s", mtbl);
635           stderr.writef(" slot = %s", slot);
636           stderr.writef(" dt = %s\n  ", mtbl[slot].p);
637         }
638         return Indexer!(Q[1..$])
639           .moveNext(cast(const(Word)*) mtbl[slot].p,
640                     slotStride,
641                     args[1..$]);
642       } else {
643         return Indexer!(Q[1..$]).move(slotStride, args[1..$]);
644       }
645     }
646 
647     static const(Word)* moveNext(P...)(const(Word)* dt, const(Word)* slotStride, P args)
648     {
649       static if (Q.length > 0) {
650         alias Q0 = Q[0];
651         static if (IsVirtual!Q0) {
652           alias arg = args[0];
653           const (Word)* mtbl = getMptr(arg);
654           auto slot = slotStride++.i;
655           auto index = mtbl[slot].i;
656           auto stride = slotStride++.i;
657           debug(traceCalls) {
658             stderr.write(Q0.stringof, ":");
659             stderr.writef(" mtbl = %s", mtbl);
660             stderr.writef(" slot = %s", slot);
661             stderr.writef(" index = %s", index);
662             stderr.writef(" stride = %s", stride);
663             stderr.writef(" : %s\n ", dt + index * stride);
664           }
665           return Indexer!(Q[1..$])
666             .moveNext(dt + index * stride,
667                       slotStride,
668                       args[1..$]);
669         } else {
670           return Indexer!(Q[1..$]).moveNext(dt, slotStride, args[1..$]);
671         }
672       } else {
673         debug(traceCalls) {
674           stderr.writef(" return %s\n ", dt);
675         }
676         return dt;
677       }
678     }
679 
680     static const(Word)* unary(P...)(P args)
681     {
682       alias Q0 = Q[0];
683       static if (IsVirtual!Q0) {
684         debug(traceCalls) {
685           stderr.write(" ", Q0.stringof, ":");
686         }
687         return getMptr(args[0]);
688       } else {
689         return Indexer!(Q[1..$]).unary(args[1..$]);
690       }
691     }
692   }
693 
694   enum code = discriminatorCode ~ dispatcherCode;
695 
696   enum discriminatorCode = `openmethods.%s %s(openmethods.MethodTag, %s);
697 `.format(This.stringof,
698          name,
699          Params.stringof[1..$-1]
700 );
701 
702   enum refAttribute = functionAttributes & FunctionAttribute.ref_ ? "ref " : "";
703 
704   enum string nonRefAttributes= `%s%s%s%s%s%s`
705     .format(
706             functionAttributes & FunctionAttribute.pure_ ? " pure" : "",
707             functionAttributes & FunctionAttribute.nothrow_ ? " nothrow" : "",
708             functionAttributes & FunctionAttribute.trusted ? " @trusted" : "",
709             functionAttributes & FunctionAttribute.safe ? " @safe" : "",
710             functionAttributes & FunctionAttribute.nogc ? " @nogc" : "",
711             functionAttributes & FunctionAttribute.system ? " @system" : "");
712 
713   static string dispatcherCode() {
714     string params;
715     string args;
716     string sep = "";
717 
718     foreach (i, p; Params) {
719       args ~= `%sarg%d`.format(sep, i);
720       params ~= `%s%s arg%d`.format(sep, p.stringof, i);
721       sep = ", ";
722     }
723 
724     return `
725 %s%s %s(%s)%s%s%s%s%s%s
726 {
727   import std.traits, openmethods;
728 
729   debug(traceCalls) {
730     import std.stdio;
731     stderr.write(info.name);
732   }
733 
734   alias Word = Runtime.Word;
735   alias Method = openmethods.%s;
736   assert(Method.info.slotStride);
737 
738   static if (openmethods.VirtualArity!(Method.QualParams) == 1) {
739     auto mptr = Method.Indexer!(Method.QualParams).unary(%s);
740     debug(traceCalls) {
741       stderr.writef("%%s %%s", mptr, Method.info.slotStride[0].i);
742     }
743     auto pf = cast(Method.Spec) mptr[Method.info.slotStride[0].i].p;
744   } else {
745     auto pf =
746       cast(Method.Spec) Method.Indexer!(Method.QualParams).move(Method.info.slotStride, %s).p;
747   }
748 
749   debug(traceCalls) {
750     import std.stdio;
751     writefln(" pf = %%s", pf);
752   }
753 
754   assert(pf);
755   return pf(%s);
756 }
757 `.format(functionAttributes & FunctionAttribute.ref_ ? "ref " : "",
758          ReturnType.stringof,
759          name,
760          params,
761          functionAttributes & FunctionAttribute.pure_ ? " pure" : "",
762          functionAttributes & FunctionAttribute.nothrow_ ? " nothrow" : "",
763          functionAttributes & FunctionAttribute.trusted ? " @trusted" : "",
764          functionAttributes & FunctionAttribute.safe ? " @safe" : "",
765          functionAttributes & FunctionAttribute.nogc ? " @nogc" : "",
766          functionAttributes & FunctionAttribute.system ? " @system" : "",
767          This.stringof,
768          args, args, args);
769   }
770 
771   shared static this() {
772     info.name = id;
773 
774     static if (Mptr == MptrInDeallocator) {
775       ++Runtime.methodsUsingDeallocator;
776     } else static if (Mptr == MptrViaHash) {
777       ++Runtime.methodsUsingHash;
778     }
779 
780     info.ambiguousCallError = &ambiguousCallError;
781     info.notImplementedError = &notImplementedError;
782 
783     foreach (QP; QualParams) {
784       int i = 0;
785       static if (IsVirtual!QP) {
786         info.vp ~= VirtualType!(QP).classinfo;
787       }
788     }
789 
790     debug(explain) {
791       writefln("registering %s", info);
792     }
793 
794     Runtime.methodInfos[&info] = &info;
795     Runtime.needUpdate = true;
796   }
797 
798   shared static ~this() {
799     debug(explain) {
800       writefln("Unregistering %s", info);
801     }
802 
803     Runtime.methodInfos.remove(&info);
804     Runtime.needUpdate = true;
805   }
806 
807   static Spec nextPtr(T...) = null;
808 }
809 
810 struct MethodTag { }
811 
812 struct Runtime
813 {
814   union Word
815   {
816     void* p;
817     Word* pw;
818     int i;
819   }
820 
821   struct MethodInfo
822   {
823     string name;
824     ClassInfo[] vp;
825     SpecInfo*[] specInfos;
826     Word* slotStride;
827     void* ambiguousCallError;
828     void* notImplementedError;
829   }
830 
831   struct SpecInfo
832   {
833     void* pf;
834     ClassInfo[] vp;
835     void** nextPtr;
836   }
837 
838   struct Method
839   {
840     MethodInfo* info;
841     Class*[] vp;
842     Spec*[] specs;
843 
844     int[] slots;
845     int[] strides;
846     GroupMap[] groups;
847     void*[] dispatchTable;
848     Word* gvDispatchTable;
849 
850     auto toString() const
851     {
852       return format("%s(%s)", info.name, vp.map!(c => c.name).join(", "));
853     }
854   }
855 
856   struct Spec
857   {
858     SpecInfo* info;
859     Class*[] params;
860 
861     auto toString() const
862     {
863       return format("(%s)", params.map!(c => c.name).join(", "));
864     }
865   }
866 
867   struct Param
868   {
869     Method* method;
870     int param;
871 
872     auto toString() const
873     {
874       return format("%s#%d", *method, param);
875     }
876   }
877 
878   struct Class
879   {
880     ClassInfo info;
881     Class*[] directBases;
882     Class*[] directDerived;
883     Class*[Class*] conforming;
884     Param[] methodParams;
885     int nextSlot = 0;
886     int firstUsedSlot = -1;
887     int[] mtbl;
888     Word* gvMtbl;
889 
890 
891     @property auto name() const
892     {
893       return info.name.split(".")[$ - 1];
894     }
895 
896     @property auto isClass()
897     {
898       return info is Object.classinfo
899         || info.base is Object.classinfo
900         || info.base !is null;
901     }
902   }
903 
904   alias Registry = MethodInfo*[MethodInfo*];
905 
906   struct Metrics
907   {
908     size_t methodTableSize, dispatchTableSize, hashTableSize;
909     ulong hashSearchAttempts;
910     typeof(StopWatch.peek()) hashSearchTime;
911 
912     auto toString() const
913     {
914       string hashMetrics;
915 
916       if (hashSearchAttempts) {
917         version (GNU) {} else {
918           hashMetrics = format(", hash table size = %s, hash found after %s attempts and %g ms", hashTableSize, hashSearchAttempts, hashSearchTime.split!("nsecs").nsecs / 1000.);
919         }
920       }
921 
922       return format("method table size: %s, dispatchTableSize: %s%s",
923                     methodTableSize, dispatchTableSize, hashMetrics);
924     }
925   }
926 
927   static __gshared Registry methodInfos;
928   static __gshared ClassInfo[] additionalClasses;
929   static __gshared Word[] gv; // Global Vector
930   static __gshared needUpdate = true;
931   static __gshared ulong hashMult;
932   static __gshared uint hashShift, hashSize;
933   static __gshared uint methodsUsingDeallocator;
934   static __gshared uint methodsUsingHash;
935 
936   Method*[] methods;
937   Class*[ClassInfo] classMap;
938   Class*[] classes;
939   Metrics metrics;
940 
941   Metrics update()
942   {
943     // Create a Method object for each method.  Create a Class object for all
944     // the classes or interfaces that occur as virtual parameters in a method,
945     // or were registered explicitly with 'registerClasses'.
946 
947     seed();
948 
949     // Create a Class object for all the classes or interfaces that derive from
950     // a class or interface that occur as virtual parameters in a method,
951     // or were registered explicitly with 'registerClasses'. Also record in
952     // each Class object all the method parameters that target it.
953 
954     debug(explain) {
955       writefln("Scooping...");
956     }
957 
958   foreach (mod; ModuleInfo) {
959       foreach (c; mod.localClasses) {
960         scoop(c);
961       }
962   }
963 
964     // Fill the 'directBases' and 'directDerived' arrays in the Class objects.
965 
966     initClasses();
967 
968     // Copy the Class objects to the 'classes' array, ensuring that derived
969     // classes and interface come after their base class and interfaces, but as
970     // close to them as possible.
971     layer();
972 
973     // Fill the 'conforming' arrays, i.e. for each class record all the classes
974     // and interfaces that are type compatible with it. Note that every class
975     // is in its own 'conforming' array.
976 
977     calculateInheritanceRelationships();
978 
979     // Check if there are classes that define the 'delete' operator.
980 
981     checkDeallocatorConflicts();
982 
983     // For each method, reserve one slot per virtual parameter in the target
984     // Class.
985 
986     allocateSlots();
987 
988     // Build dispatch tables and install the global vectors.
989 
990     buildTables();
991 
992     if (methodsUsingHash) {
993       findHash();
994     }
995 
996     installGlobalData();
997 
998     needUpdate = false;
999 
1000     return metrics;
1001   }
1002 
1003   void seed()
1004   {
1005     debug(explain) {
1006       write("Seeding...\n ");
1007     }
1008 
1009     Class* upgrade(ClassInfo ci)
1010     {
1011       Class* c;
1012       if (ci in classMap) {
1013         c = classMap[ci];
1014       } else {
1015         c = classMap[ci] = new Class(ci);
1016         debug(explain) {
1017           writef(" %s", c.name);
1018         }
1019       }
1020       return c;
1021     }
1022 
1023     foreach (mi; methodInfos.values) {
1024       auto m = new Method(mi);
1025       methods ~= m;
1026 
1027       foreach (int i, ci; mi.vp) {
1028         auto c = upgrade(ci);
1029         m.vp ~= c;
1030         c.methodParams ~= Runtime.Param(m, i);
1031       }
1032 
1033       m.specs = mi.specInfos.map!
1034         (si => new Spec(si,
1035                         si.vp.map!
1036                         (ci => upgrade(ci)).array)).array;
1037 
1038     }
1039 
1040     debug(explain) {
1041       writeln();
1042     }
1043 
1044     foreach (ci; additionalClasses) {
1045       if (ci !in classMap) {
1046         auto c = classMap[ci] = new Class(ci);
1047         debug(explain) {
1048           writefln("  %s", c.name);
1049         }
1050       }
1051     }
1052   }
1053 
1054   bool scoop(ClassInfo ci)
1055   {
1056     bool hasMethods;
1057 
1058     foreach (i; ci.interfaces) {
1059       if (scoop(i.classinfo)) {
1060         hasMethods = true;
1061       }
1062     }
1063 
1064     if (ci.base) {
1065       if (scoop(ci.base)) {
1066         hasMethods = true;
1067       }
1068     }
1069 
1070     if (ci in classMap) {
1071       hasMethods = true;
1072     } else if (hasMethods) {
1073       if (ci !in classMap) {
1074         auto c = classMap[ci] = new Class(ci);
1075         debug(explain) {
1076           writefln("  %s", c.name);
1077         }
1078       }
1079     }
1080 
1081     return hasMethods;
1082   }
1083 
1084   void initClasses()
1085   {
1086     foreach (ci, c; classMap) {
1087       foreach (i; ci.interfaces) {
1088         if (i.classinfo in classMap) {
1089           auto b = classMap[i.classinfo];
1090           c.directBases ~= b;
1091           b.directDerived ~= c;
1092         }
1093       }
1094 
1095       if (ci.base in classMap) {
1096         auto b = classMap[ci.base];
1097         c.directBases ~= b;
1098         b.directDerived ~= c;
1099       }
1100     }
1101   }
1102 
1103   void layer()
1104   {
1105     debug(explain) {
1106       writefln("Layering...");
1107     }
1108 
1109     auto v = classMap.values.filter!(c => c.directBases.empty).array;
1110     auto m = assocArray(zip(v, v));
1111 
1112     while (!v.empty) {
1113       debug(explain) {
1114         writefln("  %s", v.map!(c => c.name).join(" "));
1115       }
1116 
1117       v.sort!((a, b) => cmp(a.name, b.name) < 0);
1118       classes ~= v;
1119 
1120       foreach (c; v) {
1121         classMap.remove(c.info);
1122       }
1123 
1124       v = classMap.values.filter!(c => c.directBases.all!(b => b in m)).array;
1125 
1126       foreach (c; v) {
1127         m[c] = c;
1128       }
1129     }
1130   }
1131 
1132   void calculateInheritanceRelationships()
1133   {
1134     auto rclasses = classes.dup;
1135     reverse(rclasses);
1136 
1137     foreach (c; rclasses) {
1138       c.conforming[c] = c;
1139       foreach (d; c.directDerived) {
1140         c.conforming[d] = d;
1141         foreach (dc; d.conforming) {
1142           c.conforming[dc] = dc;
1143         }
1144       }
1145     }
1146   }
1147 
1148   void checkDeallocatorConflicts()
1149   {
1150     foreach (m; methods) {
1151       foreach (vp; m.vp) {
1152         foreach (c; vp.conforming) {
1153           if (c.info.deallocator
1154               && !(c.info.deallocator >= gv.ptr
1155                   && c.info.deallocator <  gv.ptr + gv.length)) {
1156             throw new MethodError(MethodError.DeallocatorInUse, m.info);
1157           }
1158         }
1159       }
1160     }
1161   }
1162 
1163   void allocateSlots()
1164   {
1165     debug(explain) {
1166       writeln("Allocating slots...");
1167     }
1168 
1169     foreach (c; classes) {
1170       if (!c.methodParams.empty) {
1171         debug(explain) {
1172           writefln("  %s...", c.name);
1173         }
1174 
1175         foreach (mp; c.methodParams) {
1176           int slot = c.nextSlot++;
1177 
1178           debug(explain) {
1179             writef("    for %s: allocate slot %d\n    also in", mp, slot);
1180           }
1181 
1182           if (mp.method.slots.length <= mp.param) {
1183             mp.method.slots.length = mp.param + 1;
1184           }
1185 
1186           mp.method.slots[mp.param] = slot;
1187 
1188 
1189           if (c.firstUsedSlot == -1) {
1190             c.firstUsedSlot = slot;
1191           }
1192 
1193           bool [Class*] visited;
1194           visited[c] = true;
1195 
1196           foreach (d; c.directDerived) {
1197             allocateSlotDown(d, slot, visited);
1198           }
1199 
1200           debug(explain) {
1201             writeln();
1202           }
1203         }
1204       }
1205     }
1206     foreach (c; classes) {
1207       c.mtbl.length = c.nextSlot;
1208     }
1209   }
1210 
1211   void allocateSlotDown(Class* c, int slot, bool[Class*] visited)
1212   {
1213     if (c in visited)
1214       return;
1215 
1216     debug(explain) {
1217       writef(" %s", c.name);
1218     }
1219 
1220     visited[c] = true;
1221 
1222     assert(slot >= c.nextSlot);
1223 
1224     c.nextSlot = slot + 1;
1225 
1226     if (c.firstUsedSlot == -1) {
1227       c.firstUsedSlot = slot;
1228     }
1229 
1230     foreach (b; c.directBases) {
1231       allocateSlotUp(b, slot, visited);
1232     }
1233 
1234     foreach (d; c.directDerived) {
1235       allocateSlotDown(d, slot, visited);
1236     }
1237   }
1238 
1239   void allocateSlotUp(Class* c, int slot, bool[Class*] visited)
1240   {
1241     if (c in visited)
1242       return;
1243 
1244     debug(explain) {
1245       writef(" %s", c.name);
1246     }
1247 
1248     visited[c] = true;
1249 
1250     assert(slot >= c.nextSlot);
1251 
1252     c.nextSlot = slot + 1;
1253 
1254     if (c.firstUsedSlot == -1) {
1255       c.firstUsedSlot = slot;
1256     }
1257 
1258     foreach (d; c.directBases) {
1259       allocateSlotUp(d, slot, visited);
1260     }
1261   }
1262 
1263   static bool isMoreSpecific(Spec* a, Spec* b)
1264   {
1265     bool result = false;
1266 
1267     for (int i = 0; i < a.params.length; i++) {
1268       if (a.params[i] !is b.params[i]) {
1269         if (a.params[i] in b.params[i].conforming) {
1270           result = true;
1271         } else if (b.params[i] in a.params[i].conforming) {
1272           return false;
1273         }
1274       }
1275     }
1276 
1277     return result;
1278   }
1279 
1280   static Spec*[] best(Spec*[] candidates) {
1281     Spec*[] best;
1282 
1283     foreach (spec; candidates) {
1284       for (int i = 0; i != best.length; ) {
1285         if (isMoreSpecific(spec, best[i])) {
1286           best.remove(i);
1287           best.length -= 1;
1288         } else if (isMoreSpecific(best[i], spec)) {
1289           spec = null;
1290           break;
1291         } else {
1292           ++i;
1293         }
1294       }
1295 
1296       if (spec) {
1297         best ~= spec;
1298       }
1299     }
1300 
1301     return best;
1302   }
1303 
1304   alias GroupMap = Class*[][BitArray];
1305 
1306   void buildTable(Method* m, size_t dim, GroupMap[] groups, BitArray candidates)
1307   {
1308     int groupIndex = 0;
1309 
1310     foreach (mask, group; groups[dim]) {
1311       if (dim == 0) {
1312         auto finalMask = candidates & mask;
1313         Spec*[] applicable;
1314 
1315         foreach (i, spec; m.specs) {
1316           if (finalMask[i]) {
1317             applicable ~= spec;
1318           }
1319         }
1320 
1321         debug(explain) {
1322           writefln("%*s    dim %d group %d (%s): select best of %s",
1323                    (m.vp.length - dim) * 2, "",
1324                    dim, groupIndex,
1325                    group.map!(c => c.name).join(", "),
1326                    applicable.map!(spec => spec.toString).join(", "));
1327         }
1328 
1329         auto specs = best(applicable);
1330 
1331         if (specs.length > 1) {
1332           m.dispatchTable ~= m.info.ambiguousCallError;
1333         } else if (specs.empty) {
1334           m.dispatchTable ~= m.info.notImplementedError;
1335         } else {
1336           m.dispatchTable ~= specs[0].info.pf;
1337 
1338           debug(explain) {
1339             writefln("%*s      %s: pf = %s",
1340                      (m.vp.length - dim) * 2, "",
1341                      specs.map!(spec => spec.toString).join(", "),
1342                      specs[0].info.pf);
1343           }
1344         }
1345       } else {
1346         debug(explain) {
1347           writefln("%*s    dim %d group %d (%s)",
1348                    (m.vp.length - dim) * 2, "",
1349                    dim, groupIndex,
1350                    group.map!(c => c.name).join(", "));
1351         }
1352         buildTable(m, dim - 1, groups, candidates & mask);
1353       }
1354       ++groupIndex;
1355     }
1356   }
1357 
1358   void findHash()
1359   {
1360     import std.random, std.math;
1361 
1362     void**[] vptrs;
1363 
1364     foreach (c; classes) {
1365       if (c.info.vtbl.ptr) {
1366         vptrs ~= c.info.vtbl.ptr;
1367       }
1368   }
1369 
1370     auto N = vptrs.length;
1371     StopWatch sw;
1372     sw.start();
1373 
1374     debug(explain) {
1375       writefln("  finding hash factor for %s vptrs", N);
1376     }
1377 
1378     int M;
1379     auto rnd = Random(unpredictableSeed);
1380     ulong totalAttempts;
1381 
1382     for (int room = 2; room <= 6; ++room) {
1383       M = 1;
1384 
1385       while ((1 << M) < room * N / 2) {
1386         ++M;
1387       }
1388 
1389       hashShift = 64 - M;
1390       hashSize = 1 << M;
1391       int[] buckets;
1392       buckets.length = hashSize;
1393 
1394       debug(explain) {
1395         writefln("  trying with M = %s, %s buckets", M, buckets.length);
1396       }
1397 
1398       bool found;
1399       int attempts;
1400 
1401       while (!found && attempts < 100_000) {
1402         ++attempts;
1403         ++totalAttempts;
1404         found = true;
1405         hashMult = rnd.uniform!ulong | 1;
1406         buckets[] = 0;
1407         foreach (vptr; vptrs) {
1408           auto h = hash(vptr);
1409           if (buckets[h]++) {
1410             found = false;
1411             break;
1412           }
1413         }
1414       }
1415 
1416       metrics.hashSearchAttempts = totalAttempts;
1417       metrics.hashSearchTime = sw.peek();
1418       metrics.hashTableSize = hashSize;
1419 
1420       if (found) {
1421         debug(explain) {
1422           writefln("  found %s after %s attempts and %s msecs",
1423                    hashMult, totalAttempts, metrics.hashSearchTime.split!("msecs").msecs);
1424         }
1425         return;
1426       }
1427     }
1428 
1429     throw new Error("cannot find hash factor");
1430   }
1431 
1432   static auto hash(void* p) {
1433     return cast(uint) ((hashMult * (cast(ulong) p)) >> hashShift);
1434   }
1435 
1436   void buildTables()
1437   {
1438     foreach (m; methods) {
1439       debug(explain) {
1440         writefln("Building dispatch table for %s", *m);
1441       }
1442 
1443       auto dims = m.vp.length;
1444       m.groups.length = dims;
1445 
1446       foreach (int dim, vp; m.vp) {
1447         debug(explain) {
1448           writefln("  make groups for param #%s, class %s", dim, vp.name);
1449         }
1450 
1451         foreach (conforming; vp.conforming) {
1452           if (conforming.isClass) {
1453             debug(explain) {
1454               writefln("    specs applicable to %s", conforming.name);
1455             }
1456 
1457             BitArray mask;
1458             mask.length = m.specs.length;
1459 
1460             foreach (int specIndex, spec; m.specs) {
1461               if (conforming in spec.params[dim].conforming) {
1462                 debug(explain) {
1463                   writefln("      %s", *spec);
1464                 }
1465                 mask[specIndex] = 1;
1466               }
1467             }
1468 
1469             debug(explain) {
1470               writefln("      bit mask = %s", mask);
1471             }
1472 
1473             if (mask in m.groups[dim]) {
1474               debug(explain) {
1475                 writefln("      add class %s to existing group", conforming.name, mask);
1476               }
1477               m.groups[dim][mask] ~= conforming;
1478             } else {
1479               debug(explain) {
1480                 writefln("      create new group for %s", conforming.name);
1481               }
1482               m.groups[dim][mask] = [ conforming ];
1483             }
1484           }
1485         }
1486       }
1487 
1488       int stride = 1;
1489       m.strides.length = dims - 1;
1490 
1491       foreach (int dim, vp; m.vp[1..$]) {
1492         debug(explain) {
1493           writefln("    stride for dim %s = %s", dim + 1, stride);
1494         }
1495         stride *= m.groups[dim].length;
1496         m.strides[dim] = stride;
1497       }
1498 
1499       BitArray none;
1500       none.length = m.specs.length;
1501 
1502       debug(explain) {
1503         writefln("    assign specs");
1504       }
1505 
1506       buildTable(m, dims - 1, m.groups, ~none);
1507 
1508       debug(explain) {
1509         writefln("  assign slots");
1510       }
1511 
1512       foreach (int dim, vp; m.vp) {
1513         debug(explain) {
1514           writefln("    dim %s", dim);
1515         }
1516 
1517         int i = 0;
1518 
1519         foreach (group; m.groups[dim]) {
1520           debug(explain) {
1521             writefln("      group %d (%s)",
1522                      i,
1523                      group.map!(c => c.name).join(", "));
1524           }
1525           foreach (c; group) {
1526             c.mtbl[m.slots[dim]] = i;
1527           }
1528 
1529           ++i;
1530         }
1531       }
1532     }
1533   }
1534 
1535   void installGlobalData()
1536   {
1537     auto finalSize = hashSize;
1538 
1539     foreach (m; methods) {
1540       finalSize += m.slots.length + m.strides.length;
1541       if (m.vp.length > 1) {
1542         finalSize += m.dispatchTable.length;
1543       }
1544     }
1545 
1546     foreach (c; classes) {
1547       if (c.isClass) {
1548         finalSize += c.nextSlot - c.firstUsedSlot;
1549       }
1550     }
1551 
1552     gv.length = 0;
1553     gv.reserve(finalSize);
1554 
1555     debug(explain) {
1556       void trace(T...)(string format, T args) {
1557         writef("%4s %s: ", gv.length, gv.ptr + gv.length);
1558         writef(format, args);
1559       }
1560     }
1561 
1562     debug(explain) {
1563       writefln("Initializing global vector");
1564     }
1565 
1566     if (hashSize > 0) {
1567       debug(explain)
1568         trace("hash table\n");
1569       gv.length = hashSize;
1570     }
1571 
1572     Word word;
1573 
1574     foreach (m; methods) {
1575 
1576       m.info.slotStride = gv.ptr + gv.length;
1577 
1578       debug(explain) {
1579         trace("slots and strides for %s\n", *m);
1580       }
1581 
1582       int iSlot = 0;
1583       word.i = m.slots[iSlot++];
1584       gv ~= word;
1585 
1586       while (iSlot < m.slots.length) {
1587         word.i = m.slots[iSlot];
1588         gv ~= word;
1589         word.i = m.strides[iSlot - 1];
1590         gv ~= word;
1591         ++iSlot;
1592       }
1593 
1594       if (m.info.vp.length > 1) {
1595         m.gvDispatchTable = gv.ptr + gv.length;
1596         debug(explain) {
1597           trace("and %d function pointers at %s\n",
1598                 m.dispatchTable.length, m.gvDispatchTable);
1599         }
1600         foreach (p; m.dispatchTable) {
1601           word.p = p;
1602           gv ~= word;
1603         }
1604       }
1605     }
1606 
1607     enforce(gv.length <= finalSize,
1608             format("gv.length = %s > finalSize = %s", gv.length, finalSize));
1609 
1610     foreach (c; classes) {
1611       if (c.isClass) {
1612 
1613         c.gvMtbl = gv.ptr + gv.length - c.firstUsedSlot;
1614 
1615         debug(explain) {
1616           trace("method table for %s\n", c.name);
1617         }
1618 
1619         if (methodsUsingDeallocator) {
1620           c.info.deallocator = c.gvMtbl;
1621           debug(explain) {
1622             writefln("     -> %s.deallocator", c.name);
1623           }
1624         }
1625 
1626         if (hashSize > 0) {
1627           auto h = hash(c.info.vtbl.ptr);
1628           debug(explain) {
1629             writefln("     -> %s hashTable[%d]", c.name, h);
1630           }
1631           gv[h].p = c.gvMtbl;
1632         }
1633 
1634         gv.length += c.nextSlot - c.firstUsedSlot;
1635       }
1636     }
1637 
1638     enforce(gv.length <= finalSize,
1639             format("gv.length = %s > finalSize = %s", gv.length, finalSize));
1640 
1641     foreach (m; methods) {
1642       auto slot = m.slots[0];
1643       if (m.info.vp.length == 1) {
1644         debug(explain) {
1645           writefln("  %s: 1-method, storing fp in mtbl, slot = %s", *m, slot);
1646         }
1647         int i = 0;
1648         foreach (group; m.groups[0]) {
1649           foreach (c; group) {
1650             Word* index = c.gvMtbl + slot;
1651             index.p = m.dispatchTable[i];
1652             debug(explain) {
1653               writefln("    group %s pf = %s %s", i, index.p, c.name);
1654             }
1655           }
1656           ++i;
1657         }
1658       } else {
1659         debug(explain) {
1660           writefln("  %s: %s-method, storing col* in mtbl, slot = %s",
1661                    *m, m.vp.length, slot);
1662         }
1663 
1664         foreach (int dim, vp; m.vp) {
1665           debug(explain) {
1666             writefln("    dim %s", dim);
1667           }
1668 
1669           int groupIndex = 0;
1670 
1671           foreach (group; m.groups[dim]) {
1672             debug(explain) {
1673               writefln("      group %d (%s)",
1674                        groupIndex,
1675                        group.map!(c => c.name).join(", "));
1676             }
1677 
1678             if (dim == 0) {
1679               debug(explain) {
1680                 writefln("        [%s] <- %s",
1681                          m.slots[dim],
1682                          m.gvDispatchTable + groupIndex);
1683               }
1684               foreach (c; group) {
1685                 c.gvMtbl[m.slots[dim]].p = m.gvDispatchTable + groupIndex;
1686               }
1687             } else {
1688               debug(explain) {
1689                 writefln("        [%s] <- %s",
1690                          m.slots[dim],
1691                          groupIndex);
1692               }
1693               foreach (c; group) {
1694                 c.gvMtbl[m.slots[dim]].i = groupIndex;
1695               }
1696             }
1697             ++groupIndex;
1698           }
1699         }
1700       }
1701 
1702       foreach (spec; m.specs) {
1703         auto nextSpec = findNext(spec, m.specs);
1704         *spec.info.nextPtr = nextSpec ? nextSpec.info.pf : null;
1705       }
1706     }
1707 
1708     enforce(gv.length == finalSize,
1709             format("gv.length = %s <> finalSize = %s", gv.length, finalSize));
1710   }
1711 
1712   static auto findNext(Spec* spec, Spec*[] specs)
1713   {
1714     auto candidates =
1715       best(specs.filter!(other => isMoreSpecific(spec, other)).array);
1716     return candidates.length == 1 ? candidates.front : null;
1717   }
1718 
1719   version (unittest) {
1720     int[] slots(alias MX)()
1721     {
1722       return methods.find!(m => m.info == &MX.ThisMethod.info)[0].slots;
1723     }
1724 
1725     Class* getClass(C)()
1726     {
1727       return classes.find!(c => c.info == C.classinfo)[0];
1728     }
1729   }
1730 }
1731 
1732 immutable bool hasVirtualParameters(alias F) = anySatisfy!(IsVirtual, Parameters!F);
1733 
1734 unittest
1735 {
1736   void meth(virtual!Object);
1737   static assert(hasVirtualParameters!meth);
1738   void nonmeth(Object);
1739   static assert(!hasVirtualParameters!nonmeth);
1740 }
1741 
1742 version (GNU) {
1743   template getUDAs(alias symbol, alias attribute)
1744   {
1745     import std.meta : Filter;
1746 
1747     template isDesiredUDA(alias toCheck)
1748     {
1749       static if (is(typeof(attribute)) && !__traits(isTemplate, attribute))
1750         {
1751           static if (__traits(compiles, toCheck == attribute))
1752             enum isDesiredUDA = toCheck == attribute;
1753           else
1754             enum isDesiredUDA = false;
1755         }
1756       else static if (is(typeof(toCheck)))
1757         {
1758           static if (__traits(isTemplate, attribute))
1759             enum isDesiredUDA =  isInstanceOf!(attribute, typeof(toCheck));
1760           else
1761             enum isDesiredUDA = is(typeof(toCheck) == attribute);
1762         }
1763       else static if (__traits(isTemplate, attribute))
1764         enum isDesiredUDA = isInstanceOf!(attribute, toCheck);
1765       else
1766         enum isDesiredUDA = is(toCheck == attribute);
1767     }
1768     alias getUDAs = Filter!(isDesiredUDA, __traits(getAttributes, symbol));
1769   }
1770 }
1771 
1772 string _registerMethods(alias MODULE)()
1773 {
1774   import std.array;
1775 
1776   string[] code;
1777 
1778   foreach (m; __traits(allMembers, MODULE)) {
1779     static if (is(typeof(__traits(getOverloads, MODULE, m)))) {
1780       foreach (o; __traits(getOverloads, MODULE, m)) {
1781         static if (hasVirtualParameters!o) {
1782           static if (hasUDA!(o, openmethods.mptr)) {
1783             static assert(getUDAs!(o, openmethods.mptr).length == 1,
1784                           "only une @mptr allowed");
1785             immutable index = getUDAs!(o, openmethods.mptr)[0].index;
1786           } else {
1787             immutable index = "deallocator";
1788           }
1789 
1790           auto meth =
1791             format(`openmethods.Method!("%s", %s, "%s", %s, %s)`,
1792                    index,
1793                    ReturnType!o.stringof,
1794                    m,
1795                    functionAttributes!o,
1796                    Parameters!o.stringof[1..$-1]);
1797           //code ~= format(`alias %s = %s.dispatcher;`, m, meth);
1798           // alias Method = openmethods.Method!(index, ReturnType, m, functionAttributes!o, Parameters!o);
1799           alias Method = openmethods.Method!(index, ReturnType!o, m, functionAttributes!o, Parameters!o);
1800           //code ~= `mixin(%s.code);`.format(meth);
1801           code ~= Method.dispatcherCode;
1802           code ~= Method.discriminatorCode;
1803           //code ~= format(`alias %s = %s.discriminator;`, m, meth);
1804         }
1805       }
1806     }
1807   }
1808   return join(code, "\n");
1809 }
1810 
1811 mixin template _registerSpecs(alias MODULE)
1812 {
1813   import openmethods;
1814 
1815   mixin template wrap(Meth, alias Fun)
1816   {
1817     static struct Register {
1818 
1819       static if (Meth.functionAttributes & FunctionAttribute.ref_) {
1820         static ref Meth.ReturnType wrapper(Meth.Params args) {
1821           return Fun(openmethods.castArgs!(Meth.QualParams).To!(Parameters!Fun).arglist(args).expand);
1822         }
1823       } else {
1824         static Meth.ReturnType wrapper(Meth.Params args) {
1825           return Fun(openmethods.castArgs!(Meth.QualParams).To!(Parameters!Fun).arglist(args).expand);
1826         }
1827       }
1828 
1829       static __gshared Runtime.SpecInfo si;
1830 
1831       shared static this() {
1832         si.pf = cast(void*) &wrapper;
1833 
1834         debug(explain) {
1835           import std.stdio;
1836           writefln("Registering override %s%s", Meth.name, Parameters!Fun.stringof);
1837         }
1838 
1839         foreach (i, QP; Meth.QualParams) {
1840           static if (IsVirtual!QP) {
1841             si.vp ~= Parameters!Fun[i].classinfo;
1842           }
1843         }
1844 
1845         Meth.info.specInfos ~= &si;
1846         si.nextPtr = cast(void**) &Meth.nextPtr!(Parameters!Fun);
1847 
1848         Runtime.needUpdate = true;
1849       }
1850 
1851       shared static ~this()
1852       {
1853         debug(explain) {
1854           import std.stdio;
1855           writefln("Removing override %s%s", Meth.name, Parameters!Fun.stringof);
1856         }
1857 
1858         import std.algorithm, std.array;
1859         Meth.info.specInfos = Meth.info.specInfos.filter!(p => p != &si).array;
1860         Runtime.needUpdate = true;
1861       }
1862     }
1863 
1864     __gshared Register r;
1865   }
1866 
1867   import std.traits;
1868 
1869   shared static this()
1870   {
1871     debug(explain) {
1872       import std.stdio;
1873       writefln("Registering specs from %s", MODULE.stringof);
1874     }
1875     foreach (_openmethods_m_; __traits(allMembers, MODULE)) {
1876       static if (is(typeof(__traits(getOverloads, MODULE, _openmethods_m_)))) {
1877         foreach (_openmethods_o_; __traits(getOverloads, MODULE, _openmethods_m_)) {
1878           static if (hasUDA!(_openmethods_o_, method)) {
1879             static if (is(typeof(getUDAs!(_openmethods_o_, method)[0]) == method)) {
1880               immutable _openmethods_id_ = getUDAs!(_openmethods_o_, method)[0].id;
1881             } else {
1882               static assert(_openmethods_m_[0] == '_',
1883                             _openmethods_m_ ~ ": method name must begin with an underscore, "
1884                             ~ "or be set in @method()");
1885               immutable _openmethods_id_ = _openmethods_m_[1..$];
1886             }
1887             static assert(!hasVirtualParameters!_openmethods_o_,
1888                           _openmethods_m_ ~ ": virtual! must not be used in method definitions");
1889             alias M =
1890               typeof(mixin(_openmethods_id_)(MethodTag.init,
1891                                              Parameters!(_openmethods_o_).init));
1892             mixin wrap!(M, _openmethods_o_);
1893           }
1894         }
1895       }
1896     }
1897   }
1898 
1899   shared static ~this()
1900   {
1901     debug(explain) {
1902       import std.stdio;
1903       writefln("Unregistering specs from %s", MODULE.stringof);
1904     }
1905   }
1906 }