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