Skip to content

Commit fdfa2a5

Browse files
committed
Refactor interpreter kwargs pre-logic
This logic decides whether to: * dup an incoming keyword arguments hash * replace the original argument with the dup * set or clear ruby2_keywords_hash flag * use the original, dup, or nothing for kwargs processing The conditions leading to these various actions were intermingled with performing the actions, making it difficult to adjust that logic without breaking unrelated code. The new logic defines an enum of all combinations of behavior and provides a straightforward set of if/else branches to choose an action. This allows both the interpreter and the JIT to use the same conditional logic but with each handling the actions in an appropriate way (e.g. JIT may not have an arguments array but it can replace fixed-arity positional arguments). First stage of refactoring keyword arguments logic to unify interpreter and JIT and make it easier to follow and maintain.
1 parent ee491bf commit fdfa2a5

2 files changed

Lines changed: 96 additions & 64 deletions

File tree

core/src/main/java/org/jruby/RubyHash.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,6 +2477,11 @@ public RubyHash dupFast(final ThreadContext context) {
24772477
return dup;
24782478
}
24792479

2480+
public RubyHash withRuby2Keywords(boolean ruby2Keywords) {
2481+
setRuby2KeywordHash(ruby2Keywords);
2482+
return this;
2483+
}
2484+
24802485
public boolean hasDefaultProc() {
24812486
return (flags & PROCDEFAULT_HASH_F) != 0;
24822487
}

core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -822,78 +822,105 @@ private static boolean shouldHandleKwargs(IRubyObject[] args, int callInfo) {
822822
return (callInfo & CALL_KEYWORD_EMPTY) == 0 && args.length >= 1 && args[args.length - 1] instanceof RubyHash;
823823
}
824824

825+
enum KwargsAction {
826+
REPLACE_WITH_DUP_RETURN_UNDEFINED,
827+
REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED,
828+
RETURN_DUP,
829+
RETURN_UNDEFINED,
830+
RETURN_DUP_WITHOUT_RUBY2,
831+
RETURN_DUP_WITH_RUBY2,
832+
RETURN_WITHOUT_RUBY2,
833+
RETURN_HASH
834+
}
835+
825836
private static IRubyObject receiveKeywordsHash(ThreadContext context, IRubyObject[] args, boolean hasRestArgs,
826837
boolean acceptsKeywords, boolean ruby2_keywords_method, int callInfo) {
827838
RubyHash hash = (RubyHash) args[args.length - 1];
828839

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);
837-
}
838-
839-
args[args.length - 1] = hash.dup(context);
840-
841-
// All other situations no-op
842-
return UNDEFINED;
843-
}
844-
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);
840+
switch (kwargsAction(hash, hasRestArgs, acceptsKeywords, ruby2_keywords_method, callInfo)) {
841+
case REPLACE_WITH_DUP_RETURN_UNDEFINED -> {
842+
args[args.length - 1] = hash.dupFast(context);
843+
return UNDEFINED;
844+
}
845+
case REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED -> {
846+
args[args.length - 1] = hash.dupFast(context).withRuby2Keywords(true);
847+
return UNDEFINED;
848+
}
849+
case RETURN_DUP -> {
850+
return hash.dupFast(context);
851+
}
852+
case RETURN_DUP_WITHOUT_RUBY2 -> {
853+
return hash.dupFast(context).withRuby2Keywords(false);
854+
}
855+
case RETURN_DUP_WITH_RUBY2 -> {
856+
return hash.dupFast(context).withRuby2Keywords(true);
857+
}
858+
case RETURN_WITHOUT_RUBY2 -> {
859+
return hash.withRuby2Keywords(false);
860+
}
861+
case RETURN_HASH -> {
862+
return hash;
863+
}
864+
case RETURN_UNDEFINED -> {
865+
return UNDEFINED;
866+
}
867+
case null -> throw new IllegalArgumentException("null kwargs action");
874868
}
875-
876-
// All other situations no-op
877-
return UNDEFINED;
878869
}
879870

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);
871+
private static KwargsAction kwargsAction(RubyHash hash, boolean hasRestArgs,
872+
boolean acceptsKeywords, boolean ruby2_keywords_method, int callInfo) {
873+
if (ruby2_keywords_method) {
874+
// no keywords to use in this method
875+
if ((callInfo & CALL_KEYWORD) != 0) {
876+
return KwargsAction.REPLACE_WITH_DUP_WITH_RUBY2_RETURN_UNDEFINED;
877+
} else if (hash.isRuby2KeywordHash() && (callInfo & CALL_SPLATS) != 0 && hash.isEmpty()) { // splatted empty hash...just return it
878+
return KwargsAction.RETURN_HASH;
879+
} else if (acceptsKeywords && !hash.isEmpty()) { // keywords method which has keywords
880+
return KwargsAction.RETURN_DUP;
881+
} else {
882+
return KwargsAction.RETURN_UNDEFINED;
883+
}
884+
} else if (hash.isRuby2KeywordHash()) {
885+
RubyHash hash1 = hash;
886+
// ruby2_keywords only get unmarked if it enters a method which accepts keywords.
887+
// This means methods which don't just keep that marked hash around in case it is passed
888+
// onto another method which accepts keywords.
889+
if (acceptsKeywords) {
890+
if (!hash1.isEmpty()) {
891+
return KwargsAction.RETURN_DUP_WITHOUT_RUBY2;
892+
} else {
893+
return KwargsAction.RETURN_WITHOUT_RUBY2;
894+
}
895+
} else if ((callInfo & CALL_SPLATS) != 0) {
896+
// somehow we splatted an array with an empty keyword argument attached (ruby2_keywords_hash can lead to this)
897+
if (hash1.isEmpty()) {
898+
return KwargsAction.RETURN_HASH;
899+
} else {
900+
return KwargsAction.REPLACE_WITH_DUP_RETURN_UNDEFINED;
901+
}
902+
} else if (hasKeywords(callInfo)) {
903+
if (!hash1.isEmpty()) {
904+
return KwargsAction.RETURN_DUP;
905+
} else if (hasRestArgs) {
906+
// FIXME: This is a bit gross. a real kwarg callsite if passed to a non-kwarg method but it
907+
// has a rest arg will dup the original kwarg (presumably so you cannot modify the original
908+
// kwarg hash). This should be handled during recv_rest_arg but we no longer have the info so
909+
// it happening here.
910+
return KwargsAction.REPLACE_WITH_DUP_RETURN_UNDEFINED;
911+
} else {
912+
return KwargsAction.RETURN_UNDEFINED;
913+
}
914+
} else {
915+
return KwargsAction.RETURN_UNDEFINED;
916+
}
917+
} else if (!hasKeywords(callInfo)) {
918+
return KwargsAction.RETURN_UNDEFINED;
919+
} else if (acceptsKeywords && !hash.isEmpty()) {
920+
return KwargsAction.RETURN_DUP;
921+
} else {
922+
return KwargsAction.REPLACE_WITH_DUP_RETURN_UNDEFINED;
889923
}
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;
897924
}
898925

899926
/**

0 commit comments

Comments
 (0)