Skip to content

Commit

Permalink
Prototype of fast block_given? logic using indy
Browse files Browse the repository at this point in the history
The basic idea here is to avoid pushing a frame for block_given?
when it is known to be our core implementation. Instead, we use
indy to test the block the current method was called with and
return a result directly. When the block_given? is not our
core impl, it falls back on a normal indy invocation. This allows
us to remove the frame requirement from block_given? callers, and
the cost of the block_given? call largely disappears.

Caveats:

* This is very much hacked in place, with duplicated code in
  multiple places.
* In order to pass through the block we were given, we add a Block
  parameter to the call site. If someone defines a block_given?
  in Ruby, it would receive our given block.
  • Loading branch information
headius committed Oct 24, 2023
1 parent 68e1955 commit 47c8e51
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 1 deletion.
5 changes: 5 additions & 0 deletions core/src/main/java/org/jruby/ir/instructions/CallBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ public boolean computeScopeFlags(IRScope scope, EnumSet<IRFlags> flags) {
modifiedScope |= setIRFlagsFromFrameFields(flags, frameReads);
modifiedScope |= setIRFlagsFromFrameFields(flags, frameWrites);

if (getId().equals("block_given?") && argsCount == 0) {
// remove frame read from flags for optimizable methods
flags.remove(REQUIRES_BLOCK);
}

// literal closures can be used to capture surrounding binding
if (hasLiteralClosure()) {
modifiedScope = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1191,7 +1191,11 @@ public static RubyModule findInstanceMethodContainer(ThreadContext context, Dyna
public static RubyBoolean isBlockGiven(ThreadContext context, Object blk) {
if (blk instanceof RubyProc) blk = ((RubyProc) blk).getBlock();
if (blk instanceof RubyNil) blk = Block.NULL_BLOCK;
return RubyBoolean.newBoolean(context, ((Block) blk).isGiven() );
return isBlockGiven(context, (Block) blk);
}

public static RubyBoolean isBlockGiven(ThreadContext context, Block blk) {
return RubyBoolean.newBoolean(context, blk != null && blk.isGiven() );
}

@JIT @Interp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ public void invokeSelf(String file, String scopeFieldName, CallBase call, int ar

int flags = call.getFlags();

if (id.equals("block_given?") && arity == 0) {
// specialized call site for block_given? that passes given block through
compiler.loadBlock();
compiler.adapter.invokedynamic("callFunctional:" + JavaNameMangler.mangleMethodName(id), sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, Block.class)), SelfInvokeSite.BOOTSTRAP, false, flags, file, compiler.getLastLine());
return;
}

String action = call.getCallType() == CallType.FUNCTIONAL ? "callFunctional" : "callVariable";
IRBytecodeAdapter.BlockPassType blockPassType = IRBytecodeAdapter.BlockPassType.fromIR(call);
if (blockPassType != IRBytecodeAdapter.BlockPassType.NONE) {
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/indy/InvokeSite.java
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ private IRubyObject performIndirectCall(ThreadContext context, IRubyObject self,
RubyModule sourceModule = entry.sourceModule;
DynamicMethod method = entry.method;

if (methodName.equals("block_given?") && arity == 0) {
if (method.isBuiltin()) {
return RubyBoolean.newBoolean(context, block != null && block.isGiven());
}

// omit block if it's not builtin, dispatch normally
return method.call(context, self, sourceModule, methodName, args);
}

IRRuntimeHelpers.setCallInfo(context, flags);

if (literalClosure) {
Expand Down Expand Up @@ -522,6 +531,7 @@ protected MethodHandle getHandle(IRubyObject self, CacheEntry entry) throws Thro
boolean blockGiven = signature.lastArgType() == Block.class;

MethodHandle mh = buildNewInstanceHandle(entry, self);
if (mh == null) mh = buildBlockGivenHandle(entry, self);
if (mh == null) mh = buildNotEqualHandle(entry, self);
if (mh == null) mh = Bootstrap.buildNativeHandle(this, entry, blockGiven);
if (mh == null) mh = buildJavaFieldHandle(this, entry, self);
Expand Down Expand Up @@ -647,6 +657,25 @@ MethodHandle buildNewInstanceHandle(CacheEntry entry, IRubyObject self) {
return mh;
}

MethodHandle buildBlockGivenHandle(CacheEntry entry, IRubyObject self) {
MethodHandle mh = null;
DynamicMethod method = entry.method;

if ("block_given?".equals(method.getName()) && method.isBuiltin()) {
mh = Binder.from(type())
.drop(1, 1)
.printType()
.cast(RubyBoolean.class, ThreadContext.class, Block.class)
.invokeStaticQuiet(LOOKUP, IRRuntimeHelpers.class, "isBlockGiven");

if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) {
LOG.info(name() + "\tbound as fast block given check " + Bootstrap.logMethod(method));
}
}

return mh;
}

MethodHandle buildNotEqualHandle(CacheEntry entry, IRubyObject self) {
MethodHandle mh = null;
DynamicMethod method = entry.method;
Expand Down

0 comments on commit 47c8e51

Please sign in to comment.