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