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