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