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, 252 call the override that would have been called if this one had not been 253 defined. 254 255 Examples: 256 --- 257 void inspect(virtual!Vehicle, virtual!Inspector); 258 259 @method 260 void _inspect(Vehicle v, Inspector i) 261 { 262 writeln("Inspect vehicle."); 263 } 264 265 @method 266 void _inspect(Car v, Inspector i) 267 { 268 next!inspect(v, i); 269 writeln("Inspect seat belts."); 270 } 271 272 @method 273 void _inspect(Car v, StateInspector i) 274 { 275 next!inspect(v, i); 276 writeln("Check insurance."); 277 } 278 279 ... 280 281 Vehicle car = new Car; 282 Inspector inspector = new StateInspector; 283 inspect(car, inspector); // Inspect vehicle. Inspect seat belts. Check insurance. 284 --- 285 +/ 286 287 auto next(alias F, T...)(T args) 288 { 289 alias M = typeof(F(MethodTag.init, T.init)); 290 alias Spec = M.ReturnType function(M.Params); 291 return (cast(Spec) M.nextPtr!T)(args); 292 } 293 294 /++ Used as a string mixin: register the method declarations and definitions in 295 the current module. 296 297 Examples: 298 --- 299 import openmethods; 300 mixin(registerMethods); 301 --- 302 +/ 303 304 auto registerMethods(string moduleName = __MODULE__) 305 { 306 return `static import openmethods; 307 mixin(openmethods._registerMethods!(%s)); 308 mixin openmethods._registerSpecs!(%s);`.format(moduleName, moduleName); 309 } 310 311 mixin template declareMethod(string index, ReturnType, string name, ParameterType...) 312 { 313 mixin(openmethods.Method!(index, ReturnType, name, 314 FunctionAttribute.none, ParameterType) 315 .code); 316 } 317 318 mixin template declareMethod(ReturnType, string name, ParameterType...) 319 { 320 import std.traits, std.range; 321 mixin(openmethods. 322 Method!(openmethods.MptrInDeallocator, ReturnType, name, 323 FunctionAttribute.none, "".repeat(ParameterType.length).array(), 324 ParameterType) 325 .code); 326 } 327 328 mixin template defineMethod(alias Dispatcher, alias Fun) 329 { 330 __gshared openmethods.Runtime.SpecInfo si; 331 332 shared static this() { 333 import openmethods; 334 import std.traits; 335 336 alias M = typeof(Dispatcher(MethodTag.init, Parameters!(Fun).init)); 337 338 enum wrapperCode = M.wrapperCode!(Fun); 339 mixin(wrapperCode); 340 341 mixin("alias Spec = " ~ M.pointerDeclCode ~ ";"); 342 343 enum registerSpecCode = M.registerSpecCode!(std.traits.Parameters!Fun); 344 345 debug(codeGen) 346 pragma(msg, 347 "registerSpecCode for " 348 ~ std.traits.Parameters!Fun.stringof 349 ~ ": " 350 ~ registerSpecCode); 351 mixin(registerSpecCode); 352 } 353 354 shared static ~this() 355 { 356 import openmethods; 357 import std.traits; 358 alias M = typeof(Dispatcher(MethodTag.init, std.traits.Parameters!(Fun).init)); 359 360 debug(explain) { 361 import std.stdio; 362 writefln("Removing override %s%s", M.name, Parameters!Fun.stringof); 363 } 364 365 import std.algorithm, std.array; 366 M.info.specInfos = M.info.specInfos.filter!(p => p != &si).array; 367 Runtime.needUpdate = true; 368 } 369 } 370 371 mixin template registerClasses(Classes...) { 372 shared static this() { 373 foreach (C; Classes) { 374 debug(explain) { 375 import std.stdio; 376 writefln("Registering class %s", C.stringof); 377 } 378 Runtime.additionalClasses ~= C.classinfo; 379 } 380 } 381 382 shared static ~this() 383 { 384 foreach (C; Classes) { 385 debug(explain) { 386 import std.stdio; 387 writefln("Unregistering class %s", C.stringof); 388 } 389 import std.algorithm, std.array; 390 Runtime.additionalClasses = 391 Runtime.additionalClasses.filter!(c => c != C.classinfo).array; 392 Runtime.needUpdate = true; 393 } 394 } 395 } 396 397 /++ 398 Update the runtime dispatch tables. Must be called once before calling any 399 methods. Typically this is done at the beginning of `main`. 400 +/ 401 402 Runtime.Metrics updateMethods() 403 { 404 Runtime rt; 405 return rt.update(); 406 } 407 408 bool needUpdateMethods() 409 { 410 return Runtime.needUpdate; 411 } 412 413 /++ 414 Information passed to the error handler function. 415 416 +/ 417 418 class MethodError : Error 419 { 420 this(int reason, const(Runtime.MethodInfo)* meth) { 421 super(reason.stringof); 422 this.reason = reason; 423 this.meth = meth; 424 } 425 426 @property string functionName() { return meth.name; } 427 428 enum NotImplemented = 1, AmbiguousCall = 2, DeallocatorInUse = 3; 429 const Runtime.MethodInfo* meth; 430 int reason; 431 TypeInfo[] args; 432 } 433 434 void defaultMethodErrorHandler(MethodError error) 435 { 436 import std.stdio; 437 stderr.writefln("call to %s(%s) is %s, aborting...", 438 error.functionName, 439 error.args.map!(a => a.toString).join(", "), 440 error.reason == MethodError.NotImplemented 441 ? "not implemented" : "ambiguous"); 442 import core.stdc.stdlib : abort; 443 abort(); 444 } 445 446 alias MethodErrorHandler = void function(MethodError error); 447 448 MethodErrorHandler errorHandler = &defaultMethodErrorHandler; 449 450 /++ 451 Set the error handling function to be called if an open method cannot be 452 called with the provided arguments. The default is to `abort` the program. 453 +/ 454 455 void function(MethodError error) 456 setMethodErrorHandler(void function(MethodError error) handler) 457 { 458 auto prev = errorHandler; 459 errorHandler = handler; 460 return prev; 461 } 462 463 // ============================================================================ 464 // Private parts. This doesn't exist. If you believe it does and use it, on 465 // your head be it. 466 467 enum IsVirtual(T) = false; 468 enum IsVirtual(T : virtual!U, U) = true; 469 470 alias UnqualType(T) = T; 471 alias UnqualType(T : virtual!U, U) = U; 472 473 template VirtualArity(QP...) 474 { 475 static if (QP.length == 0) { 476 enum VirtualArity = 0; 477 } else static if (IsVirtual!(QP[0])) { 478 enum VirtualArity = 1 + VirtualArity!(QP[1..$]); 479 } else { 480 enum VirtualArity = VirtualArity!(QP[1..$]); 481 } 482 } 483 484 enum IsCovariant(T) = false; 485 enum IsCovariant(T : covariant!U, U) = true; 486 487 private alias UnqualType(T : covariant!U, U) = U; 488 489 private template CallParams(T...) 490 { 491 static if (T.length == 0) { 492 alias CallParams = AliasSeq!(); 493 } else { 494 static if (IsVirtual!(T[0])) { 495 alias CallParams = AliasSeq!(UnqualType!(T[0]), CallParams!(T[1..$])); 496 } else static if (IsCovariant!(T[0])) { 497 alias CallParams = AliasSeq!(UnqualType!(T[0]), CallParams!(T[1..$])); 498 } else { 499 alias CallParams = AliasSeq!(T[0], CallParams!(T[1..$])); 500 } 501 } 502 } 503 504 immutable MptrInDeallocator = "deallocator"; 505 immutable MptrViaHash = "hash"; 506 507 template ConcatStorageClassModifiers(Modifiers...) 508 { 509 static if (Modifiers.length == 0) { 510 enum ConcatStorageClassModifiers = ""; 511 } else static if (Modifiers.length == 1) { 512 enum ConcatStorageClassModifiers = Modifiers[0]; 513 } else { 514 enum ConcatStorageClassModifiers = 515 Modifiers[0] ~ " " ~ ConcatStorageClassModifiers!(Modifiers[1..$]); 516 } 517 } 518 519 template StorageClassModifiers(alias Func) 520 { 521 template Helper(int i) 522 { 523 static if (i == Parameters!(Func).length) { 524 enum Helper = []; 525 } else { 526 enum Helper = [ ConcatStorageClassModifiers!(__traits(getParameterStorageClasses, Func, i)) ] 527 ~ Helper!(i + 1); 528 } 529 } 530 enum StorageClassModifiers = Helper!0; 531 } 532 533 template castArg(QP, int i, T) 534 { 535 static if (IsVirtual!QP) { 536 static if (is(UnqualType!QP == interface)) { 537 enum castArg = "cast(%s) a%s".format(T.stringof, i); 538 } else { 539 enum castArg = "cast(%s) cast(void*) a%s".format(T.stringof, i); 540 } 541 } else static if (IsCovariant!QP) { 542 static if (is(UnqualType!QP == class)) { 543 debug { 544 enum castArg = "cast(%s) a%s".format(T.stringof, i); 545 } else { 546 enum castArg = "cast(%s) cast(void*) a%s".format(T.stringof, i); 547 } 548 } else { 549 static assert(is(UnqualType!QP == interface), 550 "covariant argument must be a class or an interface"); 551 enum castArg = "cast(%s) a%s".format(T.stringof, i); 552 } 553 } else { 554 enum castArg = "a%s".format(i); 555 } 556 } 557 558 template castArgList(QP...) 559 { 560 template to(T...) 561 { 562 static if (QP.length == 0) { 563 enum to = []; 564 } else { 565 enum to = [ castArg!(QP[0], QP.length, T[0]) ] 566 ~ castArgList!(QP[1..$]).to!(T[1..$]); 567 } 568 } 569 } 570 571 enum typeStringOf(T) = T.stringof; 572 enum unqualTypeStringOf(T) = UnqualType!(T).stringof; 573 574 enum tupleToArray() = []; 575 enum tupleToArray(T...) = [ T[0] ] ~ tupleToArray!(T[1..$]); 576 577 enum typeList(Params...) = tupleToArray!(staticMap!(typeStringOf, Params)); 578 enum unqualTypeList(Params...) = tupleToArray!(staticMap!(unqualTypeStringOf, Params)); 579 580 template FilterVirtual(Q...) 581 { 582 template From(T...) { 583 //static assert Q.length == T.length; 584 static if (Q.length == 0) { 585 alias From = AliasSeq!(); 586 } else { 587 static if (IsVirtual!(Q[0])) { 588 alias From = AliasSeq!(T[0], FilterVirtual!(Q[1..$]).From!(T[1..$])); 589 } else { 590 alias From = FilterVirtual!(Q[1..$]).From!(T[1..$]); 591 } 592 } 593 } 594 } 595 596 string _formatAppendClassInfoCode(string cls) { 597 return "si.vp ~= %s.classinfo".format(cls); 598 } 599 600 template selectVirtualArgsCode(QP...) 601 { 602 static if (QP.length) { 603 static if (IsVirtual!(QP[0])) { 604 enum selectVirtualArgsCode = 605 [ "a%s".format(QP.length) ] ~ selectVirtualArgsCode!(QP[1..$]); 606 } else { 607 enum selectVirtualArgsCode = selectVirtualArgsCode!(QP[1..$]); 608 } 609 } else { 610 enum selectVirtualArgsCode = []; 611 } 612 } 613 614 struct Method( 615 string Mptr, R, string id, uint functionAttributes_, string[] SCM, T...) 616 { 617 alias QualParams = T; 618 alias Params = CallParams!QualParams; 619 alias ReturnType = R; 620 alias Word = Runtime.Word; 621 alias This = Method!(Mptr, R, id, functionAttributes, SCM, T); 622 623 enum name = id; 624 625 enum functionAttributes = cast(FunctionAttribute) functionAttributes_; 626 627 enum argListCode = Params.length.iota 628 .mapStatic!(i => "a%s".format(Params.length - i)) 629 .join(", "); 630 debug(codeGen) pragma(msg, "argListCode: " ~ argListCode); 631 632 enum paramListCode = zip(SCM, unqualTypeList!QualParams, Params.length.iota) 633 .mapStatic!(t => "%s %s a%s".format(t[0], t[1], Params.length - t[2])) 634 .join(", "); 635 debug(codeGen) pragma(msg, "paramListCode: " ~ paramListCode); 636 637 enum returnTypeCode = refAttribute ~ ReturnType.stringof; 638 enum returnStatementCode = is(ReturnType == void) ? "" : "return"; 639 640 enum pointerDeclCode = 641 mixin(interp!"${returnTypeCode} function(${paramListCode})"); 642 debug(codeGen) pragma(msg, "pointerDeclCode: " ~ pointerDeclCode); 643 644 enum nonRefAttributes= `%s%s%s%s%s%s` 645 .format( 646 functionAttributes & FunctionAttribute.pure_ ? " pure" : "", 647 functionAttributes & FunctionAttribute.nothrow_ ? " nothrow" : "", 648 functionAttributes & FunctionAttribute.trusted ? " @trusted" : "", 649 functionAttributes & FunctionAttribute.safe ? " @safe" : "", 650 functionAttributes & FunctionAttribute.nogc ? " @nogc" : "", 651 functionAttributes & FunctionAttribute.system ? " @system" : ""); 652 653 __gshared Runtime.MethodInfo info; 654 alias genericNextPtr = void function(); 655 __gshared genericNextPtr nextPtr(QualParams...) = null; 656 657 static R notImplementedError(QualParams...) 658 { 659 import std.meta; 660 errorHandler(new MethodError(MethodError.NotImplemented, &info)); 661 static if (!is(R == void)) { 662 return R.init; 663 } 664 } 665 666 static R ambiguousCallError(QualParams...) 667 { 668 errorHandler(new MethodError(MethodError.AmbiguousCall, &info)); 669 static if (!is(R == void)) { 670 return R.init; 671 } 672 } 673 674 static Method discriminator(MethodTag, Params); 675 676 static auto getMptr(T)(T arg) { 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 assert(mptr, format("Cannot locate method table for %s", T.classinfo.name)); 697 return mptr; 698 } 699 700 static const(Word)* move(P...)(const(Word)* slotStride, P args) { 701 alias P0 = P[0]; 702 debug(traceCalls) { 703 stderr.write("\n ", P0.stringof, ":"); 704 } 705 706 alias arg = args[0]; 707 const (Word)* mtbl = getMptr(arg); 708 auto slot = slotStride++.i; 709 auto index = mtbl[slot].i; 710 debug(traceCalls) { 711 stderr.writef(" mtbl = %s", mtbl); 712 stderr.writef(" slot = %s", slot); 713 stderr.writef(" dt = %s\n ", mtbl[slot].p); 714 } 715 return moveNext(cast(const(Word)*) mtbl[slot].p, 716 slotStride, 717 args[1..$]); 718 } 719 720 static const(Word)* moveNext(P...)( 721 const(Word)* dt, const(Word)* slotStride, P args) 722 { 723 static if (P.length > 0) { 724 alias P0 = P[0]; 725 alias arg = args[0]; 726 const (Word)* mtbl = getMptr(arg); 727 auto slot = slotStride++.i; 728 auto index = mtbl[slot].i; 729 auto stride = slotStride++.i; 730 debug(traceCalls) { 731 stderr.write(P0.stringof, ":"); 732 stderr.writef(" mtbl = %s", mtbl); 733 stderr.writef(" slot = %s", slot); 734 stderr.writef(" index = %s", index); 735 stderr.writef(" stride = %s", stride); 736 stderr.writef(" : %s\n ", dt + index * stride); 737 } 738 return moveNext(dt + index * stride, slotStride, args[1..$]); 739 } else { 740 debug(traceCalls) { 741 stderr.writef(" return %s\n ", dt); 742 } 743 return dt; 744 } 745 } 746 747 static const(Word)* unary(P)(P arg) 748 { 749 debug(traceCalls) { 750 stderr.write(" ", P.stringof, ":"); 751 } 752 return getMptr(arg); 753 } 754 755 enum discriminatorCode = `openmethods.%s %s(openmethods.MethodTag, %s); 756 `.format(This.stringof, 757 name, 758 Params.stringof[1..$-1] 759 ); 760 761 enum refAttribute = functionAttributes & FunctionAttribute.ref_ ? "ref " : ""; 762 763 enum virtualArgsCode = selectVirtualArgsCode!QualParams.join(", "); 764 765 enum attributes = 766 [functionAttributes & FunctionAttribute.pure_ ? " pure" : "", 767 functionAttributes & FunctionAttribute.nothrow_ ? " nothrow" : "", 768 functionAttributes & FunctionAttribute.trusted ? " @trusted" : "", 769 functionAttributes & FunctionAttribute.safe ? " @safe" : "", 770 functionAttributes & FunctionAttribute.nogc ? " @nogc" : "", 771 functionAttributes & FunctionAttribute.system ? " @system" : ""].join(" "); 772 773 enum dispatcherCode = mixin(interp!q{ 774 ${returnTypeCode} ${name}(${paramListCode}) ${attributes} { 775 import std.traits, openmethods; 776 777 alias Method = openmethods.${This.stringof}; 778 alias Spec = ${pointerDeclCode}; 779 780 debug(traceCalls) { 781 import std.stdio; 782 stderr.write("${name}", "${QualParams.stringof}"); 783 } 784 785 alias Word = Runtime.Word; 786 787 static if (openmethods.VirtualArity!(Method.QualParams) == 1) { 788 auto mptr = Method.unary(${virtualArgsCode}); 789 debug(traceCalls) { 790 stderr.writef("%s %s", mptr, Method.info.slotStride.i); 791 } 792 auto pf = cast(Spec) mptr[Method.info.slotStride.i].p; 793 } else { 794 assert(Method.info.slotStride.pw); 795 auto pf = 796 cast(Spec) Method.move(Method.info.slotStride.pw, ${virtualArgsCode}).p; 797 } 798 799 debug(traceCalls) { 800 import std.stdio; 801 writefln(" pf = %s", pf); 802 } 803 804 assert(pf); 805 return pf(${argListCode}); 806 } 807 }); 808 809 enum code = discriminatorCode ~ dispatcherCode; 810 811 enum castArgsCode(alias Overload) = 812 castArgList!QualParams.to!(Parameters!Overload).join(", "); 813 814 enum wrapperCode(alias Overload) = mixin(interp!q{ 815 static ${refAttribute}${ReturnType.stringof} wrapper(${paramListCode}) 816 { 817 ${returnStatementCode} ${__traits(identifier, Overload)}(${castArgsCode!Overload}); 818 } 819 }); 820 821 enum appendClassInfoCode(SpecParams...) = 822 typeList!(FilterVirtual!(QualParams).From!(SpecParams)) 823 .map!(_formatAppendClassInfoCode) 824 .join("; "); 825 826 enum registerSpecCode(SpecParams...) = 827 mixin(interp!q{ 828 si.pf = cast(void*) &wrapper; 829 830 debug(explain) { 831 import std.stdio; 832 writefln( 833 "Registering override %s%s, pf = %s", 834 M.name, M.SpecParams.stringof, &wrapper); 835 } 836 837 ${appendClassInfoCode!SpecParams}; 838 839 si.nextPtr = cast(void**) &M.nextPtr!${SpecParams.stringof}; 840 841 M.info.specInfos ~= &si; 842 openmethods.Runtime.needUpdate = true; 843 }); 844 845 shared static this() { 846 info.name = id; 847 848 static if (Mptr == MptrInDeallocator) { 849 ++Runtime.methodsUsingDeallocator; 850 } else static if (Mptr == MptrViaHash) { 851 ++Runtime.methodsUsingHash; 852 } 853 854 info.ambiguousCallError = &ambiguousCallError; 855 info.notImplementedError = ¬ImplementedError; 856 857 foreach (QP; QualParams) { 858 int i = 0; 859 static if (IsVirtual!QP) { 860 info.vp ~= UnqualType!(QP).classinfo; 861 } 862 } 863 864 debug(explain) { 865 writefln("registering %s", info); 866 } 867 868 Runtime.methodInfos[&info] = &info; 869 Runtime.needUpdate = true; 870 } 871 872 shared static ~this() { 873 debug(explain) { 874 writefln("Unregistering %s", info); 875 } 876 877 Runtime.methodInfos.remove(&info); 878 Runtime.needUpdate = true; 879 } 880 } 881 882 template MethodOf(alias Fun) { 883 static assert(hasVirtualParameters!Fun); 884 static if (hasUDA!(Fun, openmethods.mptr)) { 885 static assert(getUDAs!(Fun, openmethods.mptr).length == 1, "only une @mptr allowed"); 886 immutable index = getUDAs!(Fun, openmethods.mptr)[0].index; 887 } else { 888 immutable index = "deallocator"; 889 } 890 891 alias MethodOf = Method!(index, 892 ReturnType!Fun, 893 __traits(identifier, Fun), 894 functionAttributes!Fun, 895 StorageClassModifiers!Fun, 896 Parameters!Fun); 897 } 898 899 struct MethodTag { } 900 901 struct Runtime 902 { 903 union Word 904 { 905 void* p; 906 Word* pw; 907 int i; 908 } 909 910 struct MethodInfo 911 { 912 string name; 913 ClassInfo[] vp; 914 SpecInfo*[] specInfos; 915 Word slotStride; 916 void* ambiguousCallError; 917 void* notImplementedError; 918 } 919 920 struct SpecInfo 921 { 922 void* pf; 923 ClassInfo[] vp; 924 void** nextPtr; 925 } 926 927 struct Method 928 { 929 MethodInfo* info; 930 Class*[] vp; 931 Spec*[] specs; 932 933 int[] slots; 934 int[] strides; 935 GroupMap[] groups; 936 void*[] dispatchTable; 937 Word* gvDispatchTable; 938 939 auto toString() const 940 { 941 return format("%s(%s)", info.name, vp.map!(c => c.name).join(", ")); 942 } 943 } 944 945 struct Spec 946 { 947 SpecInfo* info; 948 Class*[] params; 949 950 auto toString() const 951 { 952 return format("(%s)", params.map!(c => c.name).join(", ")); 953 } 954 } 955 956 struct Param 957 { 958 Method* method; 959 size_t param; 960 961 auto toString() const 962 { 963 return format("%s#%d", *method, param); 964 } 965 } 966 967 struct Class 968 { 969 ClassInfo info; 970 Class*[] directBases; 971 Class*[] directDerived; 972 Class*[Class*] conforming; 973 Param[] methodParams; 974 int nextSlot = 0; 975 int firstUsedSlot = -1; 976 int[] mtbl; 977 Word* gvMtbl; 978 979 980 @property auto name() const 981 { 982 return info.name.split(".")[$ - 1]; 983 } 984 985 @property auto isClass() 986 { 987 return info is Object.classinfo 988 || info.base is Object.classinfo 989 || info.base !is null; 990 } 991 } 992 993 alias Registry = MethodInfo*[MethodInfo*]; 994 995 struct Metrics 996 { 997 size_t methodTableSize, dispatchTableSize, hashTableSize; 998 ulong hashSearchAttempts; 999 typeof(StopWatch.peek()) hashSearchTime; 1000 1001 auto toString() const 1002 { 1003 string hashMetrics; 1004 1005 if (hashSearchAttempts) { 1006 version (GNU) {} else { 1007 hashMetrics = format(", hash table size = %s, hash found after %s attempts and %g ms", hashTableSize, hashSearchAttempts, hashSearchTime.split!("nsecs").nsecs / 1000.); 1008 } 1009 } 1010 1011 return format("method table size: %s, dispatchTableSize: %s%s", 1012 methodTableSize, dispatchTableSize, hashMetrics); 1013 } 1014 } 1015 1016 static __gshared Registry methodInfos; 1017 static __gshared ClassInfo[] additionalClasses; 1018 static __gshared Word[] gv; // Global Vector 1019 static __gshared needUpdate = true; 1020 static __gshared ulong hashMult; 1021 static __gshared uint hashShift, hashSize; 1022 static __gshared uint methodsUsingDeallocator; 1023 static __gshared uint methodsUsingHash; 1024 1025 Method*[] methods; 1026 Class*[ClassInfo] classMap; 1027 Class*[] classes; 1028 Metrics metrics; 1029 1030 Metrics update() 1031 { 1032 // Create a Method object for each method. Create a Class object for all 1033 // the classes or interfaces that occur as virtual parameters in a method, 1034 // or were registered explicitly with 'registerClasses'. 1035 1036 seed(); 1037 1038 // Create a Class object for all the classes or interfaces that derive from 1039 // a class or interface that occur as virtual parameters in a method, 1040 // or were registered explicitly with 'registerClasses'. Also record in 1041 // each Class object all the method parameters that target it. 1042 1043 debug(explain) { 1044 writefln("Scooping..."); 1045 } 1046 1047 foreach (mod; ModuleInfo) { 1048 foreach (c; mod.localClasses) { 1049 scoop(c); 1050 } 1051 } 1052 1053 // Fill the 'directBases' and 'directDerived' arrays in the Class objects. 1054 1055 initClasses(); 1056 1057 // Copy the Class objects to the 'classes' array, ensuring that derived 1058 // classes and interface come after their base class and interfaces, but as 1059 // close to them as possible. 1060 layer(); 1061 1062 // Fill the 'conforming' arrays, i.e. for each class record all the classes 1063 // and interfaces that are type compatible with it. Note that every class 1064 // is in its own 'conforming' array. 1065 1066 calculateInheritanceRelationships(); 1067 1068 // Check if there are classes that define the 'delete' operator. 1069 1070 checkDeallocatorConflicts(); 1071 1072 // For each method, reserve one slot per virtual parameter in the target 1073 // Class. 1074 1075 allocateSlots(); 1076 1077 // Build dispatch tables and install the global vectors. 1078 1079 buildTables(); 1080 1081 if (methodsUsingHash) { 1082 findHash(); 1083 } 1084 1085 installGlobalData(); 1086 1087 needUpdate = false; 1088 1089 return metrics; 1090 } 1091 1092 void seed() 1093 { 1094 debug(explain) { 1095 write("Seeding...\n "); 1096 } 1097 1098 Class* upgrade(ClassInfo ci) 1099 { 1100 Class* c; 1101 if (ci in classMap) { 1102 c = classMap[ci]; 1103 } else { 1104 c = classMap[ci] = new Class(ci); 1105 debug(explain) { 1106 writef(" %s", c.name); 1107 } 1108 } 1109 return c; 1110 } 1111 1112 foreach (mi; methodInfos.values) { 1113 auto m = new Method(mi); 1114 methods ~= m; 1115 1116 foreach (size_t i, ci; mi.vp) { 1117 auto c = upgrade(ci); 1118 m.vp ~= c; 1119 c.methodParams ~= Runtime.Param(m, i); 1120 } 1121 1122 m.specs = mi.specInfos.map! 1123 (si => new Spec(si, 1124 si.vp.map! 1125 (ci => upgrade(ci)).array)).array; 1126 1127 } 1128 1129 debug(explain) { 1130 writeln(); 1131 } 1132 1133 foreach (ci; additionalClasses) { 1134 if (ci !in classMap) { 1135 auto c = classMap[ci] = new Class(ci); 1136 debug(explain) { 1137 writefln(" %s", c.name); 1138 } 1139 } 1140 } 1141 } 1142 1143 bool scoop(ClassInfo ci) 1144 { 1145 bool hasMethods; 1146 1147 foreach (i; ci.interfaces) { 1148 if (scoop(i.classinfo)) { 1149 hasMethods = true; 1150 } 1151 } 1152 1153 if (ci.base) { 1154 if (scoop(ci.base)) { 1155 hasMethods = true; 1156 } 1157 } 1158 1159 if (ci in classMap) { 1160 hasMethods = true; 1161 } else if (hasMethods) { 1162 if (ci !in classMap) { 1163 auto c = classMap[ci] = new Class(ci); 1164 debug(explain) { 1165 writefln(" %s", c.name); 1166 } 1167 } 1168 } 1169 1170 return hasMethods; 1171 } 1172 1173 void initClasses() 1174 { 1175 foreach (ci, c; classMap) { 1176 foreach (i; ci.interfaces) { 1177 if (i.classinfo in classMap) { 1178 auto b = classMap[i.classinfo]; 1179 c.directBases ~= b; 1180 b.directDerived ~= c; 1181 } 1182 } 1183 1184 if (ci.base in classMap) { 1185 auto b = classMap[ci.base]; 1186 c.directBases ~= b; 1187 b.directDerived ~= c; 1188 } 1189 } 1190 } 1191 1192 void layer() 1193 { 1194 debug(explain) { 1195 writefln("Layering..."); 1196 } 1197 1198 auto v = classMap.values.filter!(c => c.directBases.empty).array; 1199 auto m = assocArray(zip(v, v)); 1200 1201 while (!v.empty) { 1202 debug(explain) { 1203 writefln(" %s", v.map!(c => c.name).join(" ")); 1204 } 1205 1206 v.sort!((a, b) => cmp(a.name, b.name) < 0); 1207 classes ~= v; 1208 1209 foreach (c; v) { 1210 classMap.remove(c.info); 1211 } 1212 1213 v = classMap.values.filter!(c => c.directBases.all!(b => b in m)).array; 1214 1215 foreach (c; v) { 1216 m[c] = c; 1217 } 1218 } 1219 } 1220 1221 void calculateInheritanceRelationships() 1222 { 1223 auto rclasses = classes.dup; 1224 reverse(rclasses); 1225 1226 foreach (c; rclasses) { 1227 c.conforming[c] = c; 1228 foreach (d; c.directDerived) { 1229 c.conforming[d] = d; 1230 foreach (dc; d.conforming) { 1231 c.conforming[dc] = dc; 1232 } 1233 } 1234 } 1235 } 1236 1237 void checkDeallocatorConflicts() 1238 { 1239 foreach (m; methods) { 1240 foreach (vp; m.vp) { 1241 foreach (c; vp.conforming) { 1242 if (c.info.deallocator 1243 && !(c.info.deallocator >= gv.ptr 1244 && c.info.deallocator < gv.ptr + gv.length)) { 1245 throw new MethodError(MethodError.DeallocatorInUse, m.info); 1246 } 1247 } 1248 } 1249 } 1250 } 1251 1252 void allocateSlots() 1253 { 1254 debug(explain) { 1255 writeln("Allocating slots..."); 1256 } 1257 1258 foreach (c; classes) { 1259 if (!c.methodParams.empty) { 1260 debug(explain) { 1261 writefln(" %s...", c.name); 1262 } 1263 1264 foreach (mp; c.methodParams) { 1265 int slot = c.nextSlot++; 1266 1267 debug(explain) { 1268 writef(" for %s: allocate slot %d\n also in", mp, slot); 1269 } 1270 1271 if (mp.method.slots.length <= mp.param) { 1272 mp.method.slots.length = mp.param + 1; 1273 } 1274 1275 mp.method.slots[mp.param] = slot; 1276 1277 if (c.firstUsedSlot == -1) { 1278 c.firstUsedSlot = slot; 1279 } 1280 1281 bool [Class*] visited; 1282 visited[c] = true; 1283 1284 foreach (d; c.directDerived) { 1285 if (d !in visited) { 1286 allocateSlotDown(d, slot, visited); 1287 } 1288 } 1289 1290 debug(explain) { 1291 writeln(); 1292 } 1293 } 1294 } 1295 } 1296 foreach (c; classes) { 1297 c.mtbl.length = c.nextSlot; 1298 } 1299 } 1300 1301 void allocateSlotDown(Class* c, int slot, bool[Class*] visited) 1302 { 1303 debug(explain) { 1304 writef(" %s", c.name); 1305 } 1306 1307 visited[c] = true; 1308 1309 assert(slot >= c.nextSlot); 1310 1311 c.nextSlot = slot + 1; 1312 1313 if (c.firstUsedSlot == -1) { 1314 c.firstUsedSlot = slot; 1315 } 1316 1317 foreach (b; c.directBases) { 1318 if (b !in visited) { 1319 allocateSlotUp(b, slot, visited); 1320 } 1321 } 1322 1323 foreach (d; c.directDerived) { 1324 if (d !in visited) { 1325 allocateSlotDown(d, slot, visited); 1326 } 1327 } 1328 } 1329 1330 void allocateSlotUp(Class* c, int slot, bool[Class*] visited) 1331 { 1332 debug(explain) { 1333 writef(" %s", c.name); 1334 } 1335 1336 visited[c] = true; 1337 1338 assert(slot >= c.nextSlot); 1339 1340 c.nextSlot = slot + 1; 1341 1342 if (c.firstUsedSlot == -1) { 1343 c.firstUsedSlot = slot; 1344 } 1345 1346 foreach (b; c.directBases) { 1347 if (b !in visited) { 1348 allocateSlotUp(b, slot, visited); 1349 } 1350 } 1351 1352 foreach (d; c.directDerived) { 1353 if (d !in visited) { 1354 allocateSlotDown(d, slot, visited); 1355 } 1356 } 1357 } 1358 1359 static bool isMoreSpecific(Spec* a, Spec* b) 1360 { 1361 bool result = false; 1362 1363 for (int i = 0; i < a.params.length; i++) { 1364 if (a.params[i] !is b.params[i]) { 1365 if (a.params[i] in b.params[i].conforming) { 1366 result = true; 1367 } else if (b.params[i] in a.params[i].conforming) { 1368 return false; 1369 } 1370 } 1371 } 1372 1373 return result; 1374 } 1375 1376 static Spec*[] best(Spec*[] candidates) { 1377 Spec*[] best; 1378 1379 foreach (spec; candidates) { 1380 for (int i = 0; i != best.length; ) { 1381 if (isMoreSpecific(spec, best[i])) { 1382 best.remove(i); 1383 best.length -= 1; 1384 } else if (isMoreSpecific(best[i], spec)) { 1385 spec = null; 1386 break; 1387 } else { 1388 ++i; 1389 } 1390 } 1391 1392 if (spec) { 1393 best ~= spec; 1394 } 1395 } 1396 1397 return best; 1398 } 1399 1400 alias GroupMap = Class*[][BitArray]; 1401 1402 void buildTable(Method* m, size_t dim, GroupMap[] groups, BitArray candidates) 1403 { 1404 int groupIndex = 0; 1405 1406 foreach (mask, group; groups[dim]) { 1407 if (dim == 0) { 1408 auto finalMask = candidates & mask; 1409 Spec*[] applicable; 1410 1411 foreach (i, spec; m.specs) { 1412 if (finalMask[i]) { 1413 applicable ~= spec; 1414 } 1415 } 1416 1417 debug(explain) { 1418 writefln("%*s dim %d group %d (%s): select best of %s", 1419 (m.vp.length - dim) * 2, "", 1420 dim, groupIndex, 1421 group.map!(c => c.name).join(", "), 1422 applicable.map!(spec => spec.toString).join(", ")); 1423 } 1424 1425 auto specs = best(applicable); 1426 1427 if (specs.length > 1) { 1428 m.dispatchTable ~= m.info.ambiguousCallError; 1429 } else if (specs.empty) { 1430 m.dispatchTable ~= m.info.notImplementedError; 1431 } else { 1432 m.dispatchTable ~= specs[0].info.pf; 1433 1434 debug(explain) { 1435 writefln("%*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 writefln("%*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 writefln(" finding hash factor for %s vptrs", N); 1472 } 1473 1474 int M; 1475 auto rnd = Random(unpredictableSeed); 1476 ulong totalAttempts; 1477 1478 for (int room = 2; room <= 6; ++room) { 1479 M = 1; 1480 1481 while ((1 << M) < room * N / 2) { 1482 ++M; 1483 } 1484 1485 hashShift = 64 - M; 1486 hashSize = 1 << M; 1487 int[] buckets; 1488 buckets.length = hashSize; 1489 1490 debug(explain) { 1491 writefln(" 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 writefln(" 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 writefln("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 writefln(" 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 writefln(" 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 writefln(" %s", *spec); 1560 } 1561 mask[specIndex] = 1; 1562 } 1563 } 1564 1565 debug(explain) { 1566 writefln(" bit mask = %s", mask); 1567 } 1568 1569 if (mask in m.groups[dim]) { 1570 debug(explain) { 1571 writefln(" add class %s to existing group", conforming.name, mask); 1572 } 1573 m.groups[dim][mask] ~= conforming; 1574 } else { 1575 debug(explain) { 1576 writefln(" 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 writefln(" 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 writefln(" assign specs"); 1600 } 1601 1602 buildTable(m, dims - 1, m.groups, ~none); 1603 1604 debug(explain) { 1605 writefln(" assign slots"); 1606 } 1607 1608 foreach (size_t dim, vp; m.vp) { 1609 debug(explain) { 1610 writefln(" dim %s", dim); 1611 } 1612 1613 int i = 0; 1614 1615 foreach (group; m.groups[dim]) { 1616 debug(explain) { 1617 writefln(" 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 writefln("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("and %d function pointers at %s\n", 1694 m.dispatchTable.length, m.gvDispatchTable); 1695 } 1696 foreach (p; m.dispatchTable) { 1697 word.p = p; 1698 gv ~= word; 1699 } 1700 } else { 1701 m.info.slotStride.i = m.slots[0]; 1702 } 1703 } 1704 1705 enforce(gv.length <= finalSize, 1706 format("gv.length = %s > finalSize = %s", gv.length, finalSize)); 1707 1708 foreach (c; classes) { 1709 if (c.isClass) { 1710 1711 c.gvMtbl = gv.ptr + gv.length - c.firstUsedSlot; 1712 1713 debug(explain) { 1714 trace("method table for %s\n", c.name); 1715 } 1716 1717 if (methodsUsingDeallocator) { 1718 c.info.deallocator = c.gvMtbl; 1719 debug(explain) { 1720 writefln(" -> %s.deallocator", c.name); 1721 } 1722 } 1723 1724 if (hashSize > 0) { 1725 auto h = hash(c.info.vtbl.ptr); 1726 debug(explain) { 1727 writefln(" -> %s hashTable[%d]", c.name, h); 1728 } 1729 gv[h].p = c.gvMtbl; 1730 } 1731 1732 gv.length += c.nextSlot - c.firstUsedSlot; 1733 } 1734 } 1735 1736 enforce(gv.length <= finalSize, 1737 format("gv.length = %s > finalSize = %s", gv.length, finalSize)); 1738 1739 foreach (m; methods) { 1740 auto slot = m.slots[0]; 1741 if (m.info.vp.length == 1) { 1742 debug(explain) { 1743 writefln(" %s: 1-method, storing fp in mtbl, slot = %s", *m, slot); 1744 } 1745 int i = 0; 1746 foreach (group; m.groups[0]) { 1747 foreach (c; group) { 1748 Word* index = c.gvMtbl + slot; 1749 index.p = m.dispatchTable[i]; 1750 debug(explain) { 1751 writefln(" group %s pf = %s %s", i, index.p, c.name); 1752 } 1753 } 1754 ++i; 1755 } 1756 } else { 1757 debug(explain) { 1758 writefln(" %s: %s-method, storing col* in mtbl, slot = %s", 1759 *m, m.vp.length, slot); 1760 } 1761 1762 foreach (size_t dim, vp; m.vp) { 1763 debug(explain) { 1764 writefln(" dim %s", dim); 1765 } 1766 1767 int groupIndex = 0; 1768 1769 foreach (group; m.groups[dim]) { 1770 debug(explain) { 1771 writefln(" group %d (%s)", 1772 groupIndex, 1773 group.map!(c => c.name).join(", ")); 1774 } 1775 1776 if (dim == 0) { 1777 debug(explain) { 1778 writefln(" [%s] <- %s", 1779 m.slots[dim], 1780 m.gvDispatchTable + groupIndex); 1781 } 1782 foreach (c; group) { 1783 c.gvMtbl[m.slots[dim]].p = m.gvDispatchTable + groupIndex; 1784 } 1785 } else { 1786 debug(explain) { 1787 writefln(" [%s] <- %s", 1788 m.slots[dim], 1789 groupIndex); 1790 } 1791 foreach (c; group) { 1792 c.gvMtbl[m.slots[dim]].i = groupIndex; 1793 } 1794 } 1795 ++groupIndex; 1796 } 1797 } 1798 } 1799 1800 foreach (spec; m.specs) { 1801 auto nextSpec = findNext(spec, m.specs); 1802 *spec.info.nextPtr = nextSpec ? nextSpec.info.pf : null; 1803 } 1804 } 1805 1806 enforce(gv.length == finalSize, 1807 format("gv.length = %s <> finalSize = %s", gv.length, finalSize)); 1808 } 1809 1810 static auto findNext(Spec* spec, Spec*[] specs) 1811 { 1812 auto candidates = 1813 best(specs.filter!(other => isMoreSpecific(spec, other)).array); 1814 return candidates.length == 1 ? candidates.front : null; 1815 } 1816 1817 version (unittest) { 1818 int[] slots(alias MX)() 1819 { 1820 return methods.find!(m => m.info == &MX.ThisMethod.info)[0].slots; 1821 } 1822 1823 Class* getClass(C)() 1824 { 1825 return classes.find!(c => c.info == C.classinfo)[0]; 1826 } 1827 } 1828 } 1829 1830 immutable bool hasVirtualParameters(alias F) = anySatisfy!(IsVirtual, Parameters!F); 1831 1832 unittest 1833 { 1834 void meth(virtual!Object); 1835 static assert(hasVirtualParameters!meth); 1836 void nonmeth(Object); 1837 static assert(!hasVirtualParameters!nonmeth); 1838 } 1839 1840 string _registerMethods(alias MODULE)() 1841 { 1842 import std.array; 1843 1844 string[] code; 1845 1846 foreach (m; __traits(allMembers, MODULE)) { 1847 static if (is(typeof(__traits(getOverloads, MODULE, m)))) { 1848 foreach (o; __traits(getOverloads, MODULE, m)) { 1849 static if (hasVirtualParameters!o) { 1850 code ~= openmethods.MethodOf!o.code; 1851 } 1852 } 1853 } 1854 } 1855 1856 return join(code, "\n"); 1857 } 1858 1859 enum _isNamedSpec(alias spec) = is(typeof(getUDAs!(spec, method)[0]) == method); 1860 1861 template _specId(alias M, alias spec) 1862 if (_isNamedSpec!(spec)) { 1863 enum _specId = getUDAs!(spec, method)[0].id; 1864 } 1865 1866 template _specId(alias M, alias spec) 1867 if (!_isNamedSpec!(spec)) { 1868 enum _specId = M[1..$]; 1869 } 1870 1871 mixin template _registerSpecs(alias MODULE) 1872 { 1873 import openmethods; 1874 1875 mixin template Register(M, alias Fun) { 1876 1877 static struct Registrar { 1878 __gshared openmethods.Runtime.SpecInfo si; 1879 1880 shared static this() { 1881 enum wrapperCode = M.wrapperCode!(Fun); 1882 debug(codeGen) pragma(msg, "wrapperCode: " ~ wrapperCode); 1883 mixin(wrapperCode); 1884 mixin("alias Spec = " ~ M.pointerDeclCode ~ ";"); 1885 enum registerSpecCode = M.registerSpecCode!(Parameters!Fun); 1886 debug(codeGen) 1887 pragma( 1888 msg, 1889 "registerSpecCode for " 1890 ~ Parameters!Fun.stringof 1891 ~ ": " 1892 ~ registerSpecCode); 1893 mixin(registerSpecCode); 1894 } 1895 1896 shared static ~this() 1897 { 1898 debug(explain) { 1899 import std.stdio; 1900 writefln("Unregistering specs from %s", MODULE.stringof); 1901 } 1902 1903 import std.algorithm, std.array; 1904 M.info.specInfos = M.info.specInfos.filter!(p => p != &si).array; 1905 Runtime.needUpdate = true; 1906 } 1907 } 1908 1909 __gshared Registrar r; 1910 } 1911 1912 shared static this() { 1913 import std.traits; 1914 foreach (_openmethods_m_; __traits(allMembers, MODULE)) { 1915 static if (is(typeof(__traits(getOverloads, MODULE, _openmethods_m_)))) { 1916 foreach (_openmethods_o_; __traits(getOverloads, MODULE, _openmethods_m_)) { 1917 static if (hasUDA!(_openmethods_o_, method)) { 1918 static assert(getUDAs!(_openmethods_o_, method).length == 1, "only one @method attribute is allowed"); 1919 static assert(_isNamedSpec!(_openmethods_o_) || _openmethods_m_[0] == '_', 1920 _openmethods_m_ ~ ": method name must begin with an underscore, " 1921 ~ "or be set in @method()"); 1922 static assert(!hasVirtualParameters!_openmethods_o_, 1923 _openmethods_m_ ~ ": virtual! must not be used in method definitions"); 1924 mixin Register! 1925 (typeof(mixin(_specId!(_openmethods_m_, _openmethods_o_))(MethodTag.init, Parameters!(_openmethods_o_).init)), 1926 _openmethods_o_); 1927 } 1928 } 1929 } 1930 } 1931 } 1932 } 1933 1934 // string interpolation 1935 // Copied from https://github.com/Abscissa/scriptlike 1936 1937 string interp(string str)() 1938 { 1939 static import std.conv; 1940 enum State 1941 { 1942 normal, 1943 dollar, 1944 code, 1945 } 1946 1947 auto state = State.normal; 1948 1949 string buf; 1950 buf ~= '`'; 1951 1952 foreach(char c; str) 1953 final switch(state) 1954 { 1955 case State.normal: 1956 if(c == '$') 1957 // Delay copying the $ until we find out whether it's 1958 // the start of an escape sequence. 1959 state = State.dollar; 1960 else if(c == '`') { 1961 buf ~= "`~\"`\"~`"; 1962 // "`~\"`\"~`" 1963 } else 1964 buf ~= c; 1965 break; 1966 1967 case State.dollar: 1968 if(c == '{') 1969 { 1970 state = State.code; 1971 buf ~= "`~_interp_text("; 1972 } 1973 else if(c == '$') 1974 buf ~= '$'; // Copy the previous $ 1975 else 1976 { 1977 buf ~= '$'; // Copy the previous $ 1978 buf ~= c; 1979 state = State.normal; 1980 } 1981 break; 1982 1983 case State.code: 1984 if(c == '}') 1985 { 1986 buf ~= ")~`"; 1987 state = State.normal; 1988 } 1989 else 1990 buf ~= c; 1991 break; 1992 } 1993 1994 // Finish up 1995 final switch(state) 1996 { 1997 case State.normal: 1998 buf ~= '`'; 1999 break; 2000 2001 case State.dollar: 2002 buf ~= "$`"; // Copy the previous $ 2003 break; 2004 2005 case State.code: 2006 throw new Exception( 2007 "Interpolated string contains an unterminated expansion. "~ 2008 "You're missing a closing curly brace." 2009 ); 2010 } 2011 2012 return buf; 2013 } 2014 2015 string _interp_text(T...)(T args) 2016 { 2017 static import std.conv; 2018 static if(T.length == 0) 2019 return null; 2020 else 2021 return std.conv.text(args); 2022 } 2023 2024 // use 'map' inside structs 2025 // Copied from code posted by Basile B here: 2026 // https://forum.dlang.org/post/bxwlodescrnwyftwmohl@forum.dlang.org 2027 // Also explains why we need this. 2028 2029 static template mapStatic(fun...) 2030 if (fun.length >= 1) 2031 { 2032 auto mapStatic(Range)(Range r) 2033 if (isInputRange!(std.algorithm.mutation.Unqual!Range)) 2034 { 2035 import std.meta : AliasSeq, staticMap; 2036 import std.functional : unaryFun; 2037 alias RE = ElementType!(Range); 2038 alias _fun = unaryFun!fun; 2039 alias _funs = AliasSeq!(_fun); 2040 return MapResult!(_fun, Range)(r); 2041 } 2042 } 2043 2044 private static struct MapResult(alias fun, Range) 2045 { 2046 alias R = std.algorithm.mutation.Unqual!Range; 2047 R _input; 2048 2049 @property auto ref back()() 2050 { 2051 return fun(_input.back); 2052 } 2053 2054 void popBack()() 2055 { 2056 _input.popBack(); 2057 } 2058 2059 this(R input) 2060 { 2061 _input = input; 2062 } 2063 2064 @property bool empty() 2065 { 2066 return _input.empty; 2067 } 2068 2069 void popFront() 2070 { 2071 assert(!empty, "Attempting to popFront an empty map."); 2072 _input.popFront(); 2073 } 2074 2075 @property auto ref front() 2076 { 2077 assert(!empty, "Attempting to fetch the front of an empty map."); 2078 return fun(_input.front); 2079 } 2080 2081 static if (isRandomAccessRange!R) 2082 { 2083 static if (is(typeof(_input[ulong.max]))) 2084 private alias opIndex_t = ulong; 2085 else 2086 private alias opIndex_t = uint; 2087 2088 auto ref opIndex(opIndex_t index) 2089 { 2090 return fun(_input[index]); 2091 } 2092 } 2093 2094 static if (hasLength!R) 2095 { 2096 @property auto length() 2097 { 2098 return _input.length; 2099 } 2100 } 2101 2102 static if (hasSlicing!R) 2103 { 2104 static if (is(typeof(_input[ulong.max .. ulong.max]))) 2105 private alias opSlice_t = ulong; 2106 else 2107 private alias opSlice_t = uint; 2108 2109 static if (hasLength!R) 2110 { 2111 auto opSlice(opSlice_t low, opSlice_t high) 2112 { 2113 return typeof(this)(_input[low .. high]); 2114 } 2115 } 2116 } 2117 }