diff --git a/core/src/main/java/org/jruby/RubyKernel.java b/core/src/main/java/org/jruby/RubyKernel.java index 8e7e01d44a51..18f75cec05d5 100644 --- a/core/src/main/java/org/jruby/RubyKernel.java +++ b/core/src/main/java/org/jruby/RubyKernel.java @@ -971,6 +971,10 @@ public static RubyBoolean block_given_p(ThreadContext context, IRubyObject recv) return RubyBoolean.newBoolean(context, context.getCurrentFrame().getBlock().isGiven()); } + public static RubyBoolean blockGiven(ThreadContext context, IRubyObject recv, Block frameBlock) { + return RubyBoolean.newBoolean(context, frameBlock.isGiven()); + } + @JRubyMethod(name = {"sprintf", "format"}, required = 1, rest = true, checkArity = false, module = true, visibility = PRIVATE) public static IRubyObject sprintf(ThreadContext context, IRubyObject recv, IRubyObject[] args) { if (args.length == 0) { diff --git a/core/src/main/java/org/jruby/ir/targets/InvocationCompiler.java b/core/src/main/java/org/jruby/ir/targets/InvocationCompiler.java index b0392412eb01..e56d745f2567 100644 --- a/core/src/main/java/org/jruby/ir/targets/InvocationCompiler.java +++ b/core/src/main/java/org/jruby/ir/targets/InvocationCompiler.java @@ -128,4 +128,11 @@ public interface InvocationCompiler { * Stack required: none */ void setCallInfo(int flags); + + /** + * Invoke block_given? with awareness of any built-in methods. + * + * @param call a CallBase representing the call to block_given? + */ + void invokeBlockGiven(String file, String scopeFieldName, CallBase call); } 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 55135633a8eb..eb4f746ab130 100644 --- a/core/src/main/java/org/jruby/ir/targets/JVMVisitor.java +++ b/core/src/main/java/org/jruby/ir/targets/JVMVisitor.java @@ -1281,6 +1281,18 @@ called frequently enough to JIT is even more uncommon (at time of writing, this throw new NotCompilableException("ruby2_keywords can change behavior of already-compiled code"); } + // known frame-aware methods get compiled with special call sites that pass frame data through + switch (call.getName().idString()) { + case "block_given?": + if (call.getArgsCount() == 0 && !call.isPotentiallyRefined() && call.getClosureArg() == NullBlock.INSTANCE) { + /* likely a call to built-in block_given? which just checks the frame block isGiven. Only optimized + for method scopes currently. */ + m.getInvocationCompiler().invokeBlockGiven(file, jvm.methodData().scopeField, call); + handleCallResult(m, call); + return; + } + } + boolean functional = call.getCallType() == CallType.FUNCTIONAL || call.getCallType() == CallType.VARIABLE; Operand[] args = call.getCallArgs(); @@ -1323,6 +1335,10 @@ called frequently enough to JIT is even more uncommon (at time of writing, this break; } + handleCallResult(m, call); + } + + private void handleCallResult(IRBytecodeAdapter m, CallBase call) { Variable result = call.getResult(); if (result != null) { if (!omitStoreLoad) jvmStoreLocal(result); diff --git a/core/src/main/java/org/jruby/ir/targets/indy/Bootstrap.java b/core/src/main/java/org/jruby/ir/targets/indy/Bootstrap.java index 2c20d13e35d0..377f3f54982f 100644 --- a/core/src/main/java/org/jruby/ir/targets/indy/Bootstrap.java +++ b/core/src/main/java/org/jruby/ir/targets/indy/Bootstrap.java @@ -26,19 +26,26 @@ package org.jruby.ir.targets.indy; +import com.headius.invokebinder.Binder; import org.jruby.internal.runtime.methods.DynamicMethod; import org.jruby.runtime.Block; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.callsite.CacheEntry; import org.jruby.util.log.Logger; import org.jruby.util.log.LoggerFactory; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.MutableCallSite; import static java.lang.invoke.MethodHandles.Lookup; import static java.lang.invoke.MethodHandles.dropArguments; +import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodType.methodType; import static org.jruby.runtime.Helpers.arrayOf; import static org.jruby.util.CodegenUtils.p; @@ -73,4 +80,37 @@ public static Handle getBootstrapHandle(String name, Class type, String sig) { sig, false); } + + public static final Handle BLOCK_GIVEN_BOOTSTRAP = getBootstrapHandle("blockGivenBootstrap", Bootstrap.class, sig(CallSite.class, Lookup.class, String.class, MethodType.class)); + + public static CallSite blockGivenBootstrap(Lookup lookup, String name, MethodType methodType) { + MutableCallSite blockGivenSite = new MutableCallSite(methodType); + + blockGivenSite.setTarget(Binder.from(methodType).prepend(blockGivenSite, name).invokeStaticQuiet(Bootstrap.class, "blockGivenFallback")); + + return blockGivenSite; + } + + public static IRubyObject blockGivenFallback(MutableCallSite blockGivenSite, String name, ThreadContext context, IRubyObject self, Block block) throws Throwable { + CacheEntry entry = self.getMetaClass().searchWithCache("block_given?"); + MethodHandle target; + + if (entry.method.isBuiltin()) { + target = Binder.from(blockGivenSite.type()) + .permute(0, 2) + .invokeStaticQuiet(Bootstrap.class, "blockGiven"); + } else { + target = Binder.from(blockGivenSite.type()) + .permute(0, 1) + .invoke(SelfInvokeSite.bootstrap(lookup(), name, methodType(IRubyObject.class, ThreadContext.class, IRubyObject.class), 0, 0, "", 0).dynamicInvoker()); + } + + blockGivenSite.setTarget(target); + + return (IRubyObject) target.invokeExact(context, self, block); + } + + public static IRubyObject blockGiven(ThreadContext context, Block block) { + return block.isGiven() ? context.tru : context.fals; + } } diff --git a/core/src/main/java/org/jruby/ir/targets/indy/IndyInvocationCompiler.java b/core/src/main/java/org/jruby/ir/targets/indy/IndyInvocationCompiler.java index 3c980ff2719c..62fc6d98e27c 100644 --- a/core/src/main/java/org/jruby/ir/targets/indy/IndyInvocationCompiler.java +++ b/core/src/main/java/org/jruby/ir/targets/indy/IndyInvocationCompiler.java @@ -129,21 +129,26 @@ public void invokeSelf(String file, String scopeFieldName, CallBase call, int ar String action = call.getCallType() == CallType.FUNCTIONAL ? "callFunctional" : "callVariable"; IRBytecodeAdapter.BlockPassType blockPassType = IRBytecodeAdapter.BlockPassType.fromIR(call); + String callName = constructIndyCallName(action, id); if (blockPassType != IRBytecodeAdapter.BlockPassType.NONE) { if (arity == -1) { - compiler.adapter.invokedynamic(action + ':' + JavaNameMangler.mangleMethodName(id), sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT_ARRAY, Block.class)), SelfInvokeSite.BOOTSTRAP, blockPassType.literal(), flags, file, compiler.getLastLine()); + compiler.adapter.invokedynamic(callName, sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT_ARRAY, Block.class)), SelfInvokeSite.BOOTSTRAP, blockPassType.literal(), flags, file, compiler.getLastLine()); } else { - compiler.adapter.invokedynamic(action + ':' + JavaNameMangler.mangleMethodName(id), sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, arity + 1, Block.class)), SelfInvokeSite.BOOTSTRAP, blockPassType.literal(), flags, file, compiler.getLastLine()); + compiler.adapter.invokedynamic(callName, sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, arity + 1, Block.class)), SelfInvokeSite.BOOTSTRAP, blockPassType.literal(), flags, file, compiler.getLastLine()); } } else { if (arity == -1) { - compiler.adapter.invokedynamic(action + ':' + JavaNameMangler.mangleMethodName(id), sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT_ARRAY)), SelfInvokeSite.BOOTSTRAP, false, flags, file, compiler.getLastLine()); + compiler.adapter.invokedynamic(callName, sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT_ARRAY)), SelfInvokeSite.BOOTSTRAP, false, flags, file, compiler.getLastLine()); } else { - compiler.adapter.invokedynamic(action + ':' + JavaNameMangler.mangleMethodName(id), sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT, arity)), SelfInvokeSite.BOOTSTRAP, false, flags, file, compiler.getLastLine()); + compiler.adapter.invokedynamic(callName, sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT, arity)), SelfInvokeSite.BOOTSTRAP, false, flags, file, compiler.getLastLine()); } } } + private static String constructIndyCallName(String action, String id) { + return action + ':' + JavaNameMangler.mangleMethodName(id); + } + public void invokeInstanceSuper(String file, String name, int arity, boolean hasClosure, boolean literalClosure, boolean[] splatmap, int flags) { if (arity > IRBytecodeAdapter.MAX_ARGUMENTS) throw new NotCompilableException("call to instance super has more than " + IRBytecodeAdapter.MAX_ARGUMENTS + " arguments"); @@ -215,4 +220,12 @@ public void setCallInfo(int flags) { compiler.loadContext(); compiler.adapter.invokedynamic("callInfo", sig(void.class, ThreadContext.class), CallInfoBootstrap.CALL_INFO_BOOTSTRAP, flags); } + + @Override + public void invokeBlockGiven(String file, String scopeFieldName, CallBase call) { + compiler.loadContext(); + compiler.loadSelf(); + compiler.loadBlock(); + compiler.adapter.invokedynamic(constructIndyCallName("callFunctional", "block_given?"), sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, Block.class), Bootstrap.BLOCK_GIVEN_BOOTSTRAP); + } } 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 c1b4fa14577d..b0ad2377bef9 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 @@ -491,4 +491,12 @@ public void setCallInfo(int flags) { compiler.invokeIRHelper("setCallInfo", sig(void.class, ThreadContext.class, int.class)); } } + + @Override + public void invokeBlockGiven(String file, String scopeFieldName, CallBase call) { + // just invoke normally; no magic without indy + compiler.loadContext(); + compiler.loadSelf(); + invokeSelf(file, scopeFieldName, call, 0); + } }