Skip to content

Commit

Permalink
Split kwargs receive logic for specific-arity
Browse files Browse the repository at this point in the history
This method was often too big for JDK to inline, and performed
more boolean checks than necessary for the common paths. This
patch splits it up so it will be simpler and more inlinable.

* Split ruby2 kwarg logic into a separate path. It can change
  after method definition, and potentially after JIT, but it would
  be strange and buggy to not set it immediately. It can't be
  unset, so we assume if it is true at JIT time it will be true
  always.
* Fetch and compare relevant state only as-needed, rather than all
  ahead of time. This avoids extra fetches, branches, and
  comparisons that won't be used along common paths. Primarily
  this means deferring the bit checks of callInfo and the cast of
  the last argument to a RubyHash until needed.
  • Loading branch information
headius committed Feb 8, 2024
1 parent 15afb12 commit 492d539
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 13 deletions.
49 changes: 38 additions & 11 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -702,43 +702,70 @@ public static IRubyObject undefined() {
// this logic is the same as recieveKeywords but we know it will never be a keyword argument (jit
// will save %undefined as the keyword value).
@JIT // Only used for specificArity JITted methods with at least one parameter
public static IRubyObject receiveSpecificArityKeywords(ThreadContext context, StaticScope staticScope, IRubyObject last) {
IRScope scope = staticScope.getIRScope();
public static IRubyObject receiveSpecificArityKeywords(ThreadContext context, IRubyObject last) {
if (!(last instanceof RubyHash)) {
context.resetCallInfo();
return last;
}

return receiveSpecificArityHashKeywords(context, last);
}

private static IRubyObject receiveSpecificArityHashKeywords(ThreadContext context, IRubyObject last) {
int callInfo = context.resetCallInfo();
boolean isKwarg = (callInfo & CALL_KEYWORD) != 0;

/*
if ((callInfo & CALL_KEYWORD_EMPTY) != 0) {
System.out.println("HERE.2a");
return UNDEFINED;
}*/
return receiverSpecificArityKwargsCommon(context, last, callInfo, isKwarg);
}

if (!(last instanceof RubyHash)) return last;
// same as receiveSpecificArityKeywords but only used when the scope demands ruby2 keywords.
@JIT // Only used for specificArity JITted methods with at least one parameter
public static IRubyObject receiveSpecificArityRuby2Keywords(ThreadContext context, IRubyObject last) {
if (!(last instanceof RubyHash)) {
context.resetCallInfo();
return last;
}

RubyHash hash = (RubyHash) last;
return receiveSpecificArityRuby2HashKeywords(context, last);
}

private static IRubyObject receiveSpecificArityRuby2HashKeywords(ThreadContext context, IRubyObject last) {
int callInfo = context.resetCallInfo();
boolean isKwarg = (callInfo & CALL_KEYWORD) != 0;

// ruby2_keywords only get unmarked if it enters a method which accepts keywords.
// This means methods which don't just keep that marked hash around in case it is passed
// onto another method which accepts keywords.
if (scope.isRuby2Keywords() && isKwarg) {
if (isKwarg) {
// a ruby2_keywords method which happens to receive a keyword. Mark hash as ruby2_keyword
// So it can be used similarly to an ordinary hash passed in this way.

RubyHash hash = (RubyHash) last;
hash = hash.dupFast(context);
hash.setRuby2KeywordHash(true);

return hash;
} else if ((callInfo & CALL_KEYWORD_REST) != 0) {
}

return receiverSpecificArityKwargsCommon(context, last, callInfo, isKwarg);
}

private static IRubyObject receiverSpecificArityKwargsCommon(ThreadContext context, IRubyObject last, int callInfo, boolean isKwarg) {
// ruby2_keywords only get unmarked if it enters a method which accepts keywords.
// This means methods which don't just keep that marked hash around in case it is passed
// onto another method which accepts keywords.
if ((callInfo & CALL_KEYWORD_REST) != 0) {
// This is kwrest passed to a method which does not accept kwargs

// We pass empty kwrest through so kwrest does not try and slurp it up as normal argument.
// This complicates check_arity but empty ** is special case.
RubyHash hash = (RubyHash) last;
return hash;
} else if (!isKwarg) {
// This is just an ordinary hash as last argument
return last;
} else {
RubyHash hash = (RubyHash) last;
return hash.dupFast(context);
}
}
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2154,9 +2154,10 @@ public void ReceiveKeywordsInstr(ReceiveKeywordsInstr instr) {
if (argsLength >= 0) {
if (argsLength > 0) {
jvmMethod().loadContext();
jvmMethod().loadStaticScope();
jvmAdapter().aload(3 + argsLength - 1); // 3 - 0-2 are not args // FIXME: This should get abstracted
jvmMethod().invokeIRHelper("receiveSpecificArityKeywords", sig(IRubyObject.class, ThreadContext.class, StaticScope.class, IRubyObject.class));
jvmMethod().invokeIRHelper(
jvm.methodData().scope.isRuby2Keywords() ? "receiveSpecificArityRuby2Keywords" : "receiveSpecificArityKeywords",
sig(IRubyObject.class, ThreadContext.class, IRubyObject.class));
jvmAdapter().astore(3 + argsLength - 1); // 3 - 0-2 are not args // FIXME: This should get abstracted
} else {
jvmMethod().loadContext();
Expand Down

0 comments on commit 492d539

Please sign in to comment.