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