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