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