From 4769248d6c09c8439b85947a100512f7e5075e22 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 8 Feb 2024 11:26:31 -0600 Subject: [PATCH] Use static callInfo handling for better inlining Most JVM JITs can trivially inline simple static methods, and these calls are made frequently, so we flip them to static versions. --- .../main/java/org/jruby/RubyEnumerator.java | 2 +- core/src/main/java/org/jruby/RubyKernel.java | 4 +-- core/src/main/java/org/jruby/RubyStruct.java | 6 ++-- core/src/main/java/org/jruby/RubyThread.java | 2 +- core/src/main/java/org/jruby/RubyTime.java | 2 +- .../jruby/ext/coverage/CoverageModule.java | 4 +-- .../runtime/methods/AttrReaderMethod.java | 2 +- .../runtime/methods/AttrWriterMethod.java | 2 +- .../jruby/ir/runtime/IRRuntimeHelpers.java | 23 ++++----------- .../java/org/jruby/ir/targets/JVMVisitor.java | 2 +- .../ir/targets/indy/CallInfoBootstrap.java | 2 +- .../org/jruby/ir/targets/indy/InvokeSite.java | 2 +- .../simple/NormalInvocationCompiler.java | 2 +- .../java/org/jruby/runtime/ThreadContext.java | 29 +++++++++++++++++-- 14 files changed, 48 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/org/jruby/RubyEnumerator.java b/core/src/main/java/org/jruby/RubyEnumerator.java index 2faaca874de0..203ff9faab2b 100644 --- a/core/src/main/java/org/jruby/RubyEnumerator.java +++ b/core/src/main/java/org/jruby/RubyEnumerator.java @@ -207,7 +207,7 @@ public static IRubyObject __from(ThreadContext context, IRubyObject klass, IRuby int argc = Arity.checkArgumentCount(context, args, 2, 4); boolean keywords = (context.callInfo & CALL_KEYWORD) != 0 && (context.callInfo & ThreadContext.CALL_KEYWORD_EMPTY) == 0; - context.resetCallInfo(); + ThreadContext.resetCallInfo(context); // Lazy.__from(enum, method, *args, size) IRubyObject object = args[0]; diff --git a/core/src/main/java/org/jruby/RubyKernel.java b/core/src/main/java/org/jruby/RubyKernel.java index 99d4b956dd9b..3a43dab991d3 100644 --- a/core/src/main/java/org/jruby/RubyKernel.java +++ b/core/src/main/java/org/jruby/RubyKernel.java @@ -261,7 +261,7 @@ public static IRubyObject open19(ThreadContext context, IRubyObject recv, IRubyO public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) { int argc = Arity.checkArgumentCount(context, args, 1, 4); - boolean keywords = hasKeywords(context.resetCallInfo()); + boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); Ruby runtime = context.runtime; // symbol to_open = 0; boolean redirect = false; @@ -2166,7 +2166,7 @@ public static IRubyObject obj_to_enum(final ThreadContext context, IRubyObject s boolean keywords = (callInfo & ThreadContext.CALL_KEYWORD) != 0 && (callInfo & ThreadContext.CALL_KEYWORD_EMPTY) == 0; - context.resetCallInfo(); + ThreadContext.resetCallInfo(context); return enumeratorizeWithSize(context, self, method, args, sizeFn, keywords); } diff --git a/core/src/main/java/org/jruby/RubyStruct.java b/core/src/main/java/org/jruby/RubyStruct.java index 747bca5a1b8e..1907568acb5b 100644 --- a/core/src/main/java/org/jruby/RubyStruct.java +++ b/core/src/main/java/org/jruby/RubyStruct.java @@ -414,7 +414,7 @@ private void checkSize(int length) { } private void checkForKeywords(ThreadContext context, boolean keywordInit) { - if (hasKeywords(context.resetCallInfo()) && !keywordInit) { + if (hasKeywords(ThreadContext.resetCallInfo(context)) && !keywordInit) { context.runtime.getWarnings().warn("Passing only keyword arguments to Struct#initialize will behave differently from Ruby 3.2. Please use a Hash literal like .new({k: v}) instead of .new(k: v)."); } } @@ -423,7 +423,7 @@ private void checkForKeywords(ThreadContext context, boolean keywordInit) { public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { IRubyObject keywordInit = RubyStruct.getInternalVariable(classOf(), KEYWORD_INIT_VAR); checkForKeywords(context, !keywordInit.isNil()); - context.resetCallInfo(); + ThreadContext.resetCallInfo(context); modify(); checkSize(args.length); @@ -467,7 +467,7 @@ public IRubyObject initialize(ThreadContext context) { public IRubyObject initialize(ThreadContext context, IRubyObject arg0) { IRubyObject keywordInit = RubyStruct.getInternalVariable(classOf(), KEYWORD_INIT_VAR); checkForKeywords(context, !keywordInit.isNil()); - context.resetCallInfo(); + ThreadContext.resetCallInfo(context); Ruby runtime = context.runtime; if (keywordInit.isTrue()) { diff --git a/core/src/main/java/org/jruby/RubyThread.java b/core/src/main/java/org/jruby/RubyThread.java index 96a93cad1d38..adeebdb4a8b0 100644 --- a/core/src/main/java/org/jruby/RubyThread.java +++ b/core/src/main/java/org/jruby/RubyThread.java @@ -632,7 +632,7 @@ private static RubyThread adoptThread(final Ruby runtime, final ThreadService se @JRubyMethod(rest = true, visibility = PRIVATE, keywords = true) public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) { - int callInfo = context.resetCallInfo(); + int callInfo = ThreadContext.resetCallInfo(context); if (!block.isGiven()) throw context.runtime.newThreadError("must be called with a block"); if (threadImpl != ThreadLike.DUMMY) throw context.runtime.newThreadError("already initialized thread"); diff --git a/core/src/main/java/org/jruby/RubyTime.java b/core/src/main/java/org/jruby/RubyTime.java index 610cea882e61..ece075817e74 100644 --- a/core/src/main/java/org/jruby/RubyTime.java +++ b/core/src/main/java/org/jruby/RubyTime.java @@ -1717,7 +1717,7 @@ private IRubyObject initializeNow(ThreadContext context, IRubyObject zone) { @JRubyMethod(name = "initialize", optional = 7, checkArity = false, visibility = PRIVATE, keywords = true) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - boolean keywords = hasKeywords(context.resetCallInfo()); + boolean keywords = hasKeywords(ThreadContext.resetCallInfo(context)); IRubyObject zone = null; IRubyObject nil = context.nil; diff --git a/core/src/main/java/org/jruby/ext/coverage/CoverageModule.java b/core/src/main/java/org/jruby/ext/coverage/CoverageModule.java index 50d0f4e14c29..8cead28f8022 100644 --- a/core/src/main/java/org/jruby/ext/coverage/CoverageModule.java +++ b/core/src/main/java/org/jruby/ext/coverage/CoverageModule.java @@ -62,7 +62,7 @@ public static IRubyObject setup(ThreadContext context, IRubyObject self, IRubyOb } if (argc != 0) { - boolean keyword = hasKeywords(context.resetCallInfo()); + boolean keyword = hasKeywords(ThreadContext.resetCallInfo(context)); if (keyword) { RubyHash keywords = (RubyHash) TypeConverter.convertToType(args[0], runtime.getHash(), "to_hash"); @@ -151,7 +151,7 @@ public static IRubyObject result(ThreadContext context, IRubyObject self, IRubyO boolean stop = true; boolean clear = true; - if (argc > 0 && hasKeywords(context.resetCallInfo())) { + if (argc > 0 && hasKeywords(ThreadContext.resetCallInfo(context))) { RubyHash keywords = (RubyHash) TypeConverter.convertToType(args[0], runtime.getHash(), "to_hash"); stop = ArgsUtil.extractKeywordArg(context, "stop", keywords).isTrue(); clear = ArgsUtil.extractKeywordArg(context, "clear", keywords).isTrue(); diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/AttrReaderMethod.java b/core/src/main/java/org/jruby/internal/runtime/methods/AttrReaderMethod.java index 90ccbea375f9..a9423bcb1368 100644 --- a/core/src/main/java/org/jruby/internal/runtime/methods/AttrReaderMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/AttrReaderMethod.java @@ -54,7 +54,7 @@ public AttrReaderMethod(RubyModule implementationClass, Visibility visibility, S } public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) { - context.resetCallInfo(); + ThreadContext.resetCallInfo(context); IRubyObject variable = (IRubyObject) verifyAccessor(self.getMetaClass().getRealClass()).get(self); return variable == null ? context.nil : variable; } diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/AttrWriterMethod.java b/core/src/main/java/org/jruby/internal/runtime/methods/AttrWriterMethod.java index befef0339bf2..e9b136087c75 100644 --- a/core/src/main/java/org/jruby/internal/runtime/methods/AttrWriterMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/AttrWriterMethod.java @@ -56,7 +56,7 @@ public AttrWriterMethod(RubyModule implementationClass, Visibility visibility, S } public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg1) { - context.resetCallInfo(); + ThreadContext.resetCallInfo(context); verifyAccessor(self.getMetaClass().getRealClass()).set(self, arg1); return arg1; } diff --git a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java index dfb57183817d..37db454e565b 100644 --- a/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java +++ b/core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java @@ -704,7 +704,7 @@ public static IRubyObject undefined() { @JIT // Only used for specificArity JITted methods with at least one parameter public static IRubyObject receiveSpecificArityKeywords(ThreadContext context, IRubyObject last) { if (!(last instanceof RubyHash)) { - context.resetCallInfo(); + ThreadContext.clearCallInfo(context); return last; } @@ -712,7 +712,7 @@ public static IRubyObject receiveSpecificArityKeywords(ThreadContext context, IR } private static IRubyObject receiveSpecificArityHashKeywords(ThreadContext context, IRubyObject last) { - int callInfo = context.resetCallInfo(); + int callInfo = ThreadContext.resetCallInfo(context); boolean isKwarg = (callInfo & CALL_KEYWORD) != 0; return receiverSpecificArityKwargsCommon(context, last, callInfo, isKwarg); @@ -722,7 +722,7 @@ private static IRubyObject receiveSpecificArityHashKeywords(ThreadContext contex @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(); + ThreadContext.clearCallInfo(context); return last; } @@ -730,7 +730,7 @@ public static IRubyObject receiveSpecificArityRuby2Keywords(ThreadContext contex } private static IRubyObject receiveSpecificArityRuby2HashKeywords(ThreadContext context, IRubyObject last) { - int callInfo = context.resetCallInfo(); + int callInfo = ThreadContext.resetCallInfo(context); boolean isKwarg = (callInfo & CALL_KEYWORD) != 0; // ruby2_keywords only get unmarked if it enters a method which accepts keywords. @@ -780,7 +780,7 @@ public static IRubyObject receiveKeywords(ThreadContext context, StaticScope sta @Interp public static IRubyObject receiveKeywords(ThreadContext context, IRubyObject[] args, boolean hasRestArgs, boolean acceptsKeywords, boolean ruby2_keywords_method) { - int callInfo = context.resetCallInfo(); + int callInfo = ThreadContext.resetCallInfo(context); if ((callInfo & CALL_KEYWORD_EMPTY) != 0) return UNDEFINED; if (args.length < 1) return UNDEFINED; @@ -858,17 +858,6 @@ public static void setCallInfo(ThreadContext context, int flags) { context.callInfo = (context.callInfo & CALL_KEYWORD_EMPTY) | flags; } - // specific args of arity 0 does not receive kwargs so we have to reset this. - @JIT - public static void resetCallInfo(ThreadContext context) { - context.resetCallInfo(); - } - - @JIT - public static void clearCallInfo(ThreadContext context) { - context.clearCallInfo(); - } - public static void checkForExtraUnwantedKeywordArgs(ThreadContext context, final StaticScope scope, RubyHash keywordArgs) { // we do an inexpensive non-gathering scan first to see if there's a bad keyword try { @@ -1986,7 +1975,7 @@ public static RubyRegexp newLiteralRegexp(ThreadContext context, ByteList source @JIT public static RubyArray irSplat(ThreadContext context, IRubyObject ary) { Ruby runtime = context.runtime; - int callInfo = context.resetCallInfo(); + int callInfo = ThreadContext.resetCallInfo(context); IRubyObject tmp = TypeConverter.convertToTypeWithCheck(context, ary, runtime.getArray(), sites(context).to_a_checked); if (tmp.isNil()) { tmp = runtime.newArray(ary); diff --git a/core/src/main/java/org/jruby/ir/targets/JVMVisitor.java b/core/src/main/java/org/jruby/ir/targets/JVMVisitor.java index 29405b22a98d..c53639cfe6c4 100644 --- a/core/src/main/java/org/jruby/ir/targets/JVMVisitor.java +++ b/core/src/main/java/org/jruby/ir/targets/JVMVisitor.java @@ -2161,7 +2161,7 @@ public void ReceiveKeywordsInstr(ReceiveKeywordsInstr instr) { jvmAdapter().astore(3 + argsLength - 1); // 3 - 0-2 are not args // FIXME: This should get abstracted } else { jvmMethod().loadContext(); - jvmMethod().invokeIRHelper("resetCallInfo", sig(void.class, ThreadContext.class)); + jvmMethod().adapter.invokestatic(p(ThreadContext.class), "resetCallInfo", sig(void.class, ThreadContext.class)); } jvmMethod().invokeIRHelper("undefined", sig(IRubyObject.class)); } else { diff --git a/core/src/main/java/org/jruby/ir/targets/indy/CallInfoBootstrap.java b/core/src/main/java/org/jruby/ir/targets/indy/CallInfoBootstrap.java index d27bda3fda3b..5706f0558e31 100644 --- a/core/src/main/java/org/jruby/ir/targets/indy/CallInfoBootstrap.java +++ b/core/src/main/java/org/jruby/ir/targets/indy/CallInfoBootstrap.java @@ -26,7 +26,7 @@ public class CallInfoBootstrap { public static CallSite callInfoBootstrap(MethodHandles.Lookup lookup, String name, MethodType type, int callInfo) throws Throwable { MethodHandle handle; if (callInfo == 0) { - handle = lookup.findVirtual(ThreadContext.class, "clearCallInfo", methodType(void.class)); + handle = lookup.findStatic(ThreadContext.class, "clearCallInfo", methodType(void.class, ThreadContext.class)); } else { handle = lookup.findStatic(IRRuntimeHelpers.class, "setCallInfo", methodType(void.class, ThreadContext.class, int.class)); handle = insertArguments(handle, 1, callInfo); diff --git a/core/src/main/java/org/jruby/ir/targets/indy/InvokeSite.java b/core/src/main/java/org/jruby/ir/targets/indy/InvokeSite.java index a081e72ce254..e5a16a5c7ba3 100644 --- a/core/src/main/java/org/jruby/ir/targets/indy/InvokeSite.java +++ b/core/src/main/java/org/jruby/ir/targets/indy/InvokeSite.java @@ -702,7 +702,7 @@ private void finishBinding(CacheEntry entry, MethodHandle mh, IRubyObject self, SmartHandle callInfoWrapper; SmartBinder baseBinder = SmartBinder.from(signature.changeReturn(void.class)).permute("context"); if (flags == 0) { - callInfoWrapper = baseBinder.invokeVirtualQuiet(LOOKUP, "clearCallInfo"); + callInfoWrapper = baseBinder.invokeStaticQuiet(LOOKUP, ThreadContext.class, "clearCallInfo"); } else { callInfoWrapper = baseBinder.append("flags", flags).invokeStaticQuiet(LOOKUP, IRRuntimeHelpers.class, "setCallInfo"); } diff --git a/core/src/main/java/org/jruby/ir/targets/simple/NormalInvocationCompiler.java b/core/src/main/java/org/jruby/ir/targets/simple/NormalInvocationCompiler.java index 09cf32bb4ca7..c1b4fa14577d 100644 --- a/core/src/main/java/org/jruby/ir/targets/simple/NormalInvocationCompiler.java +++ b/core/src/main/java/org/jruby/ir/targets/simple/NormalInvocationCompiler.java @@ -485,7 +485,7 @@ public void asString(AsStringInstr call, String scopeFieldName, String file) { public void setCallInfo(int flags) { compiler.loadContext(); if (flags == 0) { - compiler.invokeIRHelper("clearCallInfo", sig(void.class, ThreadContext.class)); + compiler.adapter.invokestatic(p(ThreadContext.class), "clearCallInfo", sig(void.class, ThreadContext.class)); } else { compiler.adapter.ldc(flags); compiler.invokeIRHelper("setCallInfo", sig(void.class, ThreadContext.class, int.class)); diff --git a/core/src/main/java/org/jruby/runtime/ThreadContext.java b/core/src/main/java/org/jruby/runtime/ThreadContext.java index 5515a3cdb111..8eb03ae616f9 100644 --- a/core/src/main/java/org/jruby/runtime/ThreadContext.java +++ b/core/src/main/java/org/jruby/runtime/ThreadContext.java @@ -1519,18 +1519,41 @@ public IRubyObject getLocalMatchOrNil() { * Reset call info state and return the value of call info right before * it is reset. * @return the old call info + * @deprecated use the trivially-inlinable static version */ + @Deprecated public int resetCallInfo() { - int callInfo = this.callInfo; - this.callInfo = 0; + return resetCallInfo(this); + } + + /** + * Reset call info state and return the value of call info right before + * it is reset. This method is static to make it trivially inlinable on + * most JVM JITs + * + * @return the old call info + */ + public static int resetCallInfo(ThreadContext context) { + int callInfo = context.callInfo; + context.callInfo = 0; return callInfo; } /** * Clear call info state (set to 0). + * @deprecated use the trivially-inlinable static version */ + @Deprecated public void clearCallInfo() { - this.callInfo = 0; + clearCallInfo(this); + } + + /** + * Clear call info state (set to 0). This method is static to make it trivially + * inlinable on most JVM JITs + */ + public static void clearCallInfo(ThreadContext context) { + context.callInfo = 0; } public static boolean hasKeywords(int callInfo) {