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