@@ -703,65 +703,46 @@ public static IRubyObject undefined() {
703703 // call on this path and pass in the live value of ruby2_keywords from the scope.
704704 @ JIT
705705 public static IRubyObject receiveSpecificArityKeywords (ThreadContext context , IRubyObject last , boolean ruby2Keywords ) {
706- if (!(last instanceof RubyHash )) {
707- ThreadContext .clearCallInfo (context );
708- return last ;
706+ int callInfo = resetCallInfo (context );
707+ if (last instanceof RubyHash hash ) {
708+ KwargsAction kwargsAction = kwargsActionJIT (last , ruby2Keywords , callInfo );
709+ return switch (kwargsAction ) {
710+ case RETURN_DUP -> hash .dupFast (context );
711+ case RETURN_DUP_WITH_RUBY2 -> hash .dupFast (context ).withRuby2Keywords (true );
712+ case RETURN_WITHOUT_RUBY2 -> hash .withRuby2Keywords (false );
713+ case RETURN_HASH -> hash ;
714+
715+ // JIT does not have these conditions
716+ default -> throw new IllegalArgumentException ("null kwargs action" );
717+ };
709718 }
710-
711- return ruby2Keywords ?
712- receiveSpecificArityRuby2HashKeywords (context , (RubyHash ) last ) :
713- receiveSpecificArityHashKeywords (context , (RubyHash ) last );
719+ return last ;
714720 }
715721
716- private static IRubyObject receiveSpecificArityHashKeywords (ThreadContext context , RubyHash last ) {
717- int callInfo = ThreadContext .resetCallInfo (context );
718- boolean isKwarg = (callInfo & CALL_KEYWORD ) != 0 ;
722+ public static KwargsAction kwargsActionJIT (IRubyObject last , boolean ruby2Keywords , int callInfo ) {
723+ boolean isKwarg = hasKeywords (callInfo );
724+ if (ruby2Keywords ) {
725+ // ruby2_keywords only get unmarked if it enters a method which accepts keywords.
726+ // This means methods which don't just keep that marked hash around in case it is passed
727+ // onto another method which accepts keywords.
728+ if (isKwarg ) {
729+ // a ruby2_keywords method which happens to receive a keyword. Mark hash as ruby2_keyword
730+ // So it can be used similarly to an ordinary hash passed in this way.
731+ return KwargsAction .RETURN_DUP_WITH_RUBY2 ;
732+ }
719733
720- if ((callInfo & CALL_SPLATS ) != 0 && last .isRuby2KeywordHash ()) {
721- return last .dupFast (context );
722- }
734+ return KwargsAction .RETURN_HASH ;
735+ } else {
736+ if ((callInfo & CALL_SPLATS ) != 0 && ((RubyHash ) last ).isRuby2KeywordHash ()) {
737+ return KwargsAction .RETURN_DUP ;
738+ }
723739
724- return isKwarg ?
725- last .dupFast (context ) :
726- last ;
727- }
740+ if (isKwarg ) {
741+ return KwargsAction .RETURN_DUP ;
742+ }
728743
729- private static IRubyObject receiveSpecificArityRuby2HashKeywords (ThreadContext context , RubyHash last ) {
730- int callInfo = ThreadContext .resetCallInfo (context );
731- boolean isKwarg = (callInfo & CALL_KEYWORD ) != 0 ;
732-
733- // ruby2_keywords only get unmarked if it enters a method which accepts keywords.
734- // This means methods which don't just keep that marked hash around in case it is passed
735- // onto another method which accepts keywords.
736- if (isKwarg ) {
737- // a ruby2_keywords method which happens to receive a keyword. Mark hash as ruby2_keyword
738- // So it can be used similarly to an ordinary hash passed in this way.
739- RubyHash hash = last .dupFast (context );
740- hash .setRuby2KeywordHash (true );
741- return hash ;
744+ return KwargsAction .RETURN_HASH ;
742745 }
743-
744- return last ;
745- }
746-
747- /**
748- * Simplified receiveKeywords when:
749- * receiver is not a ruby2_keywords method.
750- * receiver does not accept keywords.
751- * there's no rest argument.
752- *
753- * @param context
754- * @param args
755- * @return the prepared kwargs hash, or UNDEFINED as a sigil for no kwargs
756- */
757- @ JIT
758- public static IRubyObject receiveNormalKeywordsNoRestNoKeywords (ThreadContext context , IRubyObject [] args ) {
759- int callInfo = ThreadContext .resetCallInfo (context );
760- if (shouldHandleKwargs (args , callInfo ) && (callInfo & CALL_SPLATS ) != 0 ) {
761- return receiveKeywordsWithSplatsNoRestNoKeywords (context , args );
762- }
763-
764- return UNDEFINED ;
765746 }
766747
767748 /**
@@ -795,105 +776,110 @@ public static IRubyObject receiveKeywords(ThreadContext context, IRubyObject[] a
795776 return UNDEFINED ;
796777 }
797778
798- private static IRubyObject receiveKeywordsWithSplatsNoRestNoKeywords (ThreadContext context , IRubyObject [] args ) {
799- RubyHash hash = (RubyHash ) args [args .length - 1 ];
800-
801- if (hash .isRuby2KeywordHash ()) {
802- if (hash .isEmpty ()) {
803- // case where we somehow (hash.clear) a marked ruby2_keyword. We pass it as keyword even in non-keyword
804- // accepting methods so it is subtracted from the arity count. Normally empty keyword arguments are not
805- // passed along but ruby2_keyword is a strange case since it is mutable by users.
806- return hash ;
807- }
808-
809- clearTrailingHashRuby2Keywords (context , args , hash );
810- }
811-
812- return UNDEFINED ;
813- }
814-
815- private static void clearTrailingHashRuby2Keywords (ThreadContext context , IRubyObject [] args , RubyHash hash ) {
816- RubyHash newHash = hash .dupFast (context );
817- newHash .setRuby2KeywordHash (false );
818- args [args .length - 1 ] = newHash ;
819- }
820-
821779 private static boolean shouldHandleKwargs (IRubyObject [] args , int callInfo ) {
822780 return (callInfo & CALL_KEYWORD_EMPTY ) == 0 && args .length >= 1 && args [args .length - 1 ] instanceof RubyHash ;
823781 }
824782
783+ enum KwargsAction {
784+ REPLACE_WITH_DUP_RETURN_UNDEFINED ,
785+ REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED ,
786+ RETURN_DUP ,
787+ RETURN_UNDEFINED ,
788+ RETURN_DUP_WITHOUT_RUBY2 ,
789+ RETURN_DUP_WITH_RUBY2 ,
790+ RETURN_WITHOUT_RUBY2 ,
791+ RETURN_HASH
792+ }
793+
825794 private static IRubyObject receiveKeywordsHash (ThreadContext context , IRubyObject [] args , boolean hasRestArgs ,
826795 boolean acceptsKeywords , boolean ruby2_keywords_method , int callInfo ) {
827796 RubyHash hash = (RubyHash ) args [args .length - 1 ];
828797
829- if (ruby2_keywords_method ) {
830- return receiveKeywordsHashRuby2KeywordsMethod (context , args , acceptsKeywords , hash , callInfo );
831- } else if (hash .isRuby2KeywordHash ()) {
832- return receiveKeywordsHashRubyKeywordsHash (context , args , hasRestArgs , acceptsKeywords , callInfo , hash );
833- } else if ((callInfo & CALL_KEYWORD ) == 0 ) {
834- return UNDEFINED ; // we did not pass keywords to process
835- } else if (acceptsKeywords && !hash .isEmpty ()) { // normal case kwargs passed and not empty
836- return hash .dupFast (context );
798+ switch (kwargsAction (hash , hasRestArgs , acceptsKeywords , ruby2_keywords_method , callInfo )) {
799+ case REPLACE_WITH_DUP_RETURN_UNDEFINED -> {
800+ args [args .length - 1 ] = hash .dupFast (context );
801+ return UNDEFINED ;
802+ }
803+ case REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED -> {
804+ args [args .length - 1 ] = hash .dupFast (context ).withRuby2Keywords (true );
805+ return UNDEFINED ;
806+ }
807+ case RETURN_DUP -> {
808+ return hash .dupFast (context );
809+ }
810+ case RETURN_DUP_WITHOUT_RUBY2 -> {
811+ return hash .dupFast (context ).withRuby2Keywords (false );
812+ }
813+ case RETURN_DUP_WITH_RUBY2 -> {
814+ return hash .dupFast (context ).withRuby2Keywords (true );
815+ }
816+ case RETURN_WITHOUT_RUBY2 -> {
817+ return hash .withRuby2Keywords (false );
818+ }
819+ case RETURN_HASH -> {
820+ return hash ;
821+ }
822+ case RETURN_UNDEFINED -> {
823+ return UNDEFINED ;
824+ }
825+ case null -> throw new IllegalArgumentException ("null kwargs action" );
837826 }
838-
839- args [args .length - 1 ] = hash .dup (context );
840-
841- // All other situations no-op
842- return UNDEFINED ;
843827 }
844828
845- private static IRubyObject receiveKeywordsHashRubyKeywordsHash (ThreadContext context , IRubyObject [] args ,
846- boolean hasRestArgs , boolean acceptsKeywords ,
847- int callInfo , RubyHash hash ) {
848- // ruby2_keywords only get unmarked if it enters a method which accepts keywords.
849- // This means methods which don't just keep that marked hash around in case it is passed
850- // onto another method which accepts keywords.
851- if (acceptsKeywords ) {
852- if (!hash .isEmpty ()) hash = hash .dupFast (context );
853- hash .setRuby2KeywordHash (false );
854- return hash ;
855- }
856-
857- if ((callInfo & CALL_SPLATS ) != 0 ) {
858- // somehow we splatted an array with an empty keyword argument attached (ruby2_keywords_hash can lead to this)
859- if (hash .isEmpty ()) return hash ;
860-
861- args [args .length - 1 ] = hash .dup (context );
862- return UNDEFINED ;
863- }
864-
865- boolean explicitKeywords = (callInfo & CALL_KEYWORD ) != 0 ;
866- if (explicitKeywords ) {
867- if (!hash .isEmpty ()) return hash .dupFast (context );
868-
869- // FIXME: This is a bit gross. a real kwarg callsite if passed to a non-kwarg method but it
870- // has a rest arg will dup the original kwarg (presumably so you cannot modify the original
871- // kwarg hash). This should be handled during recv_rest_arg but we no longer have the info so
872- // it happening here.
873- if (hasRestArgs ) args [args .length - 1 ] = hash .dup (context );
874- }
875-
876- // All other situations no-op
877- return UNDEFINED ;
878- }
829+ private static KwargsAction kwargsAction (RubyHash hash , boolean hasRestArgs ,
830+ boolean acceptsKeywords , boolean ruby2_keywords_method , int callInfo ) {
831+ boolean empty = hash .isEmpty ();
879832
880- private static IRubyObject receiveKeywordsHashRuby2KeywordsMethod (ThreadContext context , IRubyObject [] args ,
881- boolean acceptsKeywords , RubyHash hash ,
882- int callInfo ) {
883- if ((callInfo & CALL_KEYWORD ) != 0 ) { // We have passed in kwargs
884- setTrailingHashRuby2Keywords (context , args , hash );
885- } else if (hash .isRuby2KeywordHash () && (callInfo & CALL_SPLATS ) != 0 && hash .isEmpty ()) { // splatted empty hash...just return it
886- return hash ;
887- } else if (acceptsKeywords && !hash .isEmpty ()) { // keywords method which has keywords
888- return hash .dupFast (context );
833+ if (ruby2_keywords_method ) {
834+ // no keywords to use in this method
835+ if (hasKeywords (callInfo )) {
836+ return KwargsAction .REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED ;
837+ } else if (hash .isRuby2KeywordHash () && (callInfo & CALL_SPLATS ) != 0 && empty ) { // splatted empty hash...just return it
838+ return KwargsAction .RETURN_HASH ;
839+ } else if (acceptsKeywords && !empty ) { // keywords method which has keywords
840+ return KwargsAction .RETURN_DUP ;
841+ } else {
842+ return KwargsAction .RETURN_UNDEFINED ;
843+ }
844+ } else if (hash .isRuby2KeywordHash ()) {
845+ // ruby2_keywords only get unmarked if it enters a method which accepts keywords.
846+ // This means methods which don't just keep that marked hash around in case it is passed
847+ // onto another method which accepts keywords.
848+ if (acceptsKeywords ) {
849+ if (!empty ) {
850+ return KwargsAction .RETURN_DUP_WITHOUT_RUBY2 ;
851+ } else {
852+ return KwargsAction .RETURN_WITHOUT_RUBY2 ;
853+ }
854+ } else if ((callInfo & CALL_SPLATS ) != 0 ) {
855+ // somehow we splatted an array with an empty keyword argument attached (ruby2_keywords_hash can lead to this)
856+ if (empty ) {
857+ return KwargsAction .RETURN_HASH ;
858+ } else {
859+ return KwargsAction .REPLACE_WITH_DUP_RETURN_UNDEFINED ;
860+ }
861+ } else if (hasKeywords (callInfo )) {
862+ if (!empty ) {
863+ return KwargsAction .RETURN_DUP ;
864+ } else if (hasRestArgs ) {
865+ // FIXME: This is a bit gross. a real kwarg callsite if passed to a non-kwarg method but it
866+ // has a rest arg will dup the original kwarg (presumably so you cannot modify the original
867+ // kwarg hash). This should be handled during recv_rest_arg but we no longer have the info so
868+ // it happening here.
869+ return KwargsAction .REPLACE_WITH_DUP_RETURN_UNDEFINED ;
870+ } else {
871+ return KwargsAction .RETURN_UNDEFINED ;
872+ }
873+ } else {
874+ return KwargsAction .RETURN_UNDEFINED ;
875+ }
876+ } else if (!hasKeywords (callInfo )) {
877+ return KwargsAction .RETURN_UNDEFINED ;
878+ } else if (acceptsKeywords && !empty ) {
879+ return KwargsAction .RETURN_DUP ;
880+ } else {
881+ return KwargsAction .REPLACE_WITH_DUP_RETURN_UNDEFINED ;
889882 }
890- return UNDEFINED ; // no keywords to use in this method
891- }
892-
893- private static void setTrailingHashRuby2Keywords (ThreadContext context , IRubyObject [] args , RubyHash hash ) {
894- hash = hash .dupFast (context );
895- hash .setRuby2KeywordHash (true );
896- args [args .length - 1 ] = hash ;
897883 }
898884
899885 /**
0 commit comments