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