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 = ¬ImplementedError; 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 }