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