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