Skip to content

Commit

Permalink
Merge pull request jruby#7762 from kares/fix-java-super-slowness_9.3
Browse files Browse the repository at this point in the history
[fix] avoid walking constructor instructions on every call
  • Loading branch information
kares authored Aug 8, 2023
2 parents dc24342 + 753a0e5 commit cc1011a
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 89 deletions.
15 changes: 4 additions & 11 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -1565,7 +1565,7 @@ public final CacheEntry searchWithCache(String id, boolean cacheUndef) {
}

// MRI: method_entry_resolve_refinement
private final CacheEntry searchWithCacheAndRefinements(String id, boolean cacheUndef, StaticScope refinedScope) {
private CacheEntry searchWithCacheAndRefinements(String id, boolean cacheUndef, StaticScope refinedScope) {
CacheEntry entry = searchWithCache(id, cacheUndef);

if (entry.method.isRefined()) {
Expand Down Expand Up @@ -1838,18 +1838,11 @@ public CacheEntry searchMethodEntryInner(String id) {
}

/**
* Searches for a method up until the superclass, but include modules. This is
* for Concrete java ctor initialization
* TODO: add a cache?
* Searches for a method up until the superclass, but include modules.
*/
@Deprecated
public DynamicMethod searchMethodLateral(String id) {
// int token = generation;
// This flattens some of the recursion that would be otherwise be necessary.
// Used to recurse up the class hierarchy which got messy with prepend.
for (RubyModule module = this; module != null && (module == this || (module instanceof IncludedModuleWrapper)); module = module.getSuperClass()) {
// Only recurs if module is an IncludedModuleWrapper.
// This way only the recursion needs to be handled differently on
// IncludedModuleWrapper.
for (RubyModule module = this; (module == this || (module instanceof IncludedModuleWrapper)); module = module.getSuperClass()) {
DynamicMethod method = module.searchMethodCommon(id);
if (method != null) return method.isNull() ? null : method;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
public SplitSuperState<MethodSplitState> startSplitSuperCall(ThreadContext context, IRubyObject self,
RubyModule clazz, String name, IRubyObject[] args, Block block) {
// TODO: check if IR method, or is it guaranteed?
InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor();
if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this
ExitableInterpreterContext ic = ((IRMethod) getIRScope()).builtInterpreterContextForJavaConstructor();
if (ic == null) return null; // no super call/can't split this

MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name);
MethodSplitState state = new MethodSplitState(context, ic, clazz, self, name);

// TODO: JIT?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ protected void printMethodIR() {
public SplitSuperState<MethodSplitState> startSplitSuperCall(ThreadContext context, IRubyObject self,
RubyModule clazz, String name, IRubyObject[] args, Block block) {
// TODO: check if IR method, or is it guaranteed?
InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor();
if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this
ExitableInterpreterContext ic = ((IRMethod) getIRScope()).builtInterpreterContextForJavaConstructor();
if (ic == null) return null; // no super call/can't split this

MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name);
MethodSplitState state = new MethodSplitState(context, ic, clazz, self, name);

// TODO: JIT?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,10 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i
public SplitSuperState<MethodSplitState> startSplitSuperCall(ThreadContext context, IRubyObject self,
RubyModule clazz, String name, IRubyObject[] args, Block block) {
// TODO: check if IR method, or is it guaranteed?
InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor();
if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this
ExitableInterpreterContext ic = ((IRMethod) getIRScope()).builtInterpreterContextForJavaConstructor();
if (ic == null) return null; // no super call/can't split this

MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name);

if (IRRuntimeHelpers.isDebug()) doDebug(); // TODO?
MethodSplitState state = new MethodSplitState(context, ic, clazz, self, name);

ExitableReturn result = INTERPRET_METHOD(state, args, block);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,11 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i
public SplitSuperState<MethodSplitState> startSplitSuperCall(ThreadContext context, IRubyObject self,
RubyModule clazz, String name, IRubyObject[] args, Block block) {
// TODO: check if IR method, or is it guaranteed?
InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor();
if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this
// 2 -> IRMethod
ExitableInterpreterContext ic = ((IRMethod) getIRScope()).builtInterpreterContextForJavaConstructor();
if (ic == null) return null; // no super call/can't split this

MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name);

if (IRRuntimeHelpers.isDebug()) doDebug(); // TODO?
MethodSplitState state = new MethodSplitState(context, ic, clazz, self, name);

// TODO: JIT?

Expand Down
35 changes: 30 additions & 5 deletions core/src/main/java/org/jruby/ir/IRMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,33 @@ public InterpreterContext builtInterpreterContext() {
*
* @return appropriate interpretercontext
*/
public synchronized InterpreterContext builtInterperterContextForJavaConstructor() {
InterpreterContext interpreterContext = builtInterpreterContext();
public ExitableInterpreterContext builtInterpreterContextForJavaConstructor() {
ExitableInterpreterContext interpreterContextForJavaConstructor = this.interpreterContextForJavaConstructor;
if (interpreterContextForJavaConstructor == null) {
synchronized (this) {
interpreterContextForJavaConstructor = this.interpreterContextForJavaConstructor;
if (interpreterContextForJavaConstructor == null) {
interpreterContextForJavaConstructor = builtInterpreterContextForJavaConstructorImpl();
this.interpreterContextForJavaConstructor = interpreterContextForJavaConstructor;
}
}
}
return interpreterContextForJavaConstructor == ExitableInterpreterContext.NULL ? null : interpreterContextForJavaConstructor;
}

private volatile ExitableInterpreterContext interpreterContextForJavaConstructor;

if (usesSuper()) { // We know at least one super is in here somewhere
private synchronized ExitableInterpreterContext builtInterpreterContextForJavaConstructorImpl() {
final InterpreterContext interpreterContext = builtInterpreterContext();
if (usesSuper()) {
// We know at least one super is in here somewhere
int ipc = 0;
int superIPC = -1;
CallBase superCall = null;
Map<Label, Integer> labels = new HashMap<>();
List<Label> earlyJumps = new ArrayList<>();

for(Instr instr: interpreterContext.getInstructions()) {
for (Instr instr: interpreterContext.getInstructions()) {
if (instr instanceof CallBase && ((CallBase) instr).getCallType() == CallType.SUPER) {
// We have already found one super call already. No analysis yet to figure out if this is
// still ok or not so we will error.
Expand Down Expand Up @@ -186,7 +202,16 @@ public synchronized InterpreterContext builtInterperterContextForJavaConstructor
}
}

return interpreterContext;
return ExitableInterpreterContext.NULL;
}

/**
* This method was renamed (due a typo).
* @see #builtInterpreterContextForJavaConstructor()
*/
@Deprecated
public ExitableInterpreterContext builtInterperterContextForJavaConstructor() {
return builtInterpreterContextForJavaConstructor();
}

final InterpreterContext lazilyAcquireInterpreterContext() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,33 @@
package org.jruby.ir.interpreter;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;

import org.jruby.ir.IRFlags;
import org.jruby.ir.IRScope;
import org.jruby.ir.instructions.CallBase;
import org.jruby.ir.instructions.Instr;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

public class ExitableInterpreterContext extends InterpreterContext {

public static final ExitableInterpreterContext NULL = new ExitableInterpreterContext(null, null, 0, null, null, 0);

private final static ExitableInterpreterEngine EXITABLE_INTERPRETER = new ExitableInterpreterEngine();

private CallBase superCall;
private int exitIPC;
private final CallBase superCall;
private final int exitIPC;

public ExitableInterpreterContext(InterpreterContext originalIC, CallBase superCall, int exitIPC) {
super(originalIC.getScope(), Arrays.asList(originalIC.getInstructions()),
originalIC.getTemporaryVariableCount(), originalIC.getFlags());
this(originalIC.getScope(), Arrays.asList(originalIC.getInstructions()), originalIC.getTemporaryVariableCount(), originalIC.getFlags(), superCall, exitIPC);
}

private ExitableInterpreterContext(IRScope scope, List<Instr> instructions, int temporaryVariableCount, EnumSet<IRFlags> flags, CallBase superCall, int exitIPC) {
super(scope, instructions, temporaryVariableCount, flags);

this.superCall = superCall;
this.exitIPC = exitIPC;
Expand All @@ -58,8 +68,7 @@ public int getExitIPC() {
}

@Override
public ExitableInterpreterEngine getEngine()
{
public ExitableInterpreterEngine getEngine() {
return EXITABLE_INTERPRETER;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public class InterpreterContext {
protected boolean hasExplicitCallProtocol; // Only can be true in Full+
protected boolean dynamicScopeEliminated; // Only can be true in Full+
private boolean reuseParentDynScope; // Only can be true in Full+
private boolean metaClassBodyScope;
private final boolean metaClassBodyScope;

private InterpreterEngine engine;
public final Supplier<List<Instr>> instructionsCallback;
private EnumSet<IRFlags> flags;
private final EnumSet<IRFlags> flags;

private final IRScope scope;

Expand Down
99 changes: 50 additions & 49 deletions core/src/main/java/org/jruby/java/proxies/ConcreteJavaProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@
import java.util.Map;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.AbstractIRMethod;
import org.jruby.internal.runtime.SplitSuperState;
import org.jruby.internal.runtime.methods.DynamicMethod;
Expand All @@ -45,20 +43,17 @@
import org.jruby.javasupport.Java;
import org.jruby.javasupport.Java.JCreateMethod;
import org.jruby.javasupport.Java.JCtorCache;
import org.jruby.javasupport.JavaConstructor;
import org.jruby.javasupport.JavaObject;
import org.jruby.javasupport.proxy.JavaProxyClass;
import org.jruby.javasupport.proxy.JavaProxyConstructor;
import org.jruby.javasupport.proxy.ReifiedJavaProxy;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.JavaInternalBlockBody;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.Signature;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.CacheEntry;

public class ConcreteJavaProxy extends JavaProxy {

Expand Down Expand Up @@ -209,12 +204,12 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
*/
public static class StaticJCreateMethod extends JavaMethodNBlock {

private Constructor<? extends ReifiedJavaProxy> withBlock;
private DynamicMethod oldInit;
private final Constructor<? extends ReifiedJavaProxy> withBlock;
final DynamicMethod oldInit;

StaticJCreateMethod(RubyModule cls, Constructor<? extends ReifiedJavaProxy> withBlock2, DynamicMethod oldinit) {
super(cls, PUBLIC, "__jcreate_static!");
this.withBlock = withBlock2;
StaticJCreateMethod(RubyModule implClass, Constructor<? extends ReifiedJavaProxy> javaProxyConstructor, DynamicMethod oldinit) {
super(implClass, PUBLIC, "__jcreate_static!");
this.withBlock = javaProxyConstructor;
this.oldInit = oldinit;
}

Expand All @@ -236,10 +231,6 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
return self;
}

public DynamicMethod getOriginal() {
return oldInit;
}

public static void tryInstall(Ruby runtime, RubyClass clazz, JavaProxyClass proxyClass,
Class<? extends ReifiedJavaProxy> reified, boolean overwriteInitialize) {
try {
Expand Down Expand Up @@ -335,77 +326,87 @@ public static final class SplitCtorData {
* Picks and converts arguments for the super call
* Leaves ctorIndex and arguments ready for the super call
*/
public SplitCtorData(IRubyObject[] args, JCtorCache cache, Ruby rt) {
public SplitCtorData(IRubyObject[] args, JCtorCache cache, Ruby runtime) {
rbarguments = args;
if (cache == null) { // (ruby < ruby < java) super call from one IRO to another IRO ctor
ctorIndex = -1;
arguments = null;
} else {
ctorIndex = JCreateMethod.forTypes(args, cache, rt);
ctorIndex = JCreateMethod.forTypes(args, cache, runtime);
arguments = RubyToJavaInvoker.convertArguments(cache.constructors[ctorIndex], args);
}
}

public SplitCtorData(IRubyObject[] args, JCtorCache cache, Ruby rt, AbstractIRMethod air2, SplitSuperState<?> state2,
Block blk2) {
this(args, cache, rt);
air = air2;
state =state2;
blk = blk2;
public SplitCtorData(IRubyObject[] args, JCtorCache cache, Ruby runtime, AbstractIRMethod method, SplitSuperState<?> state,
Block block) {
this(args, cache, runtime);
this.method = method;
this.state =state;
blk = block;
}

public SplitCtorData(IRubyObject[] args, JCtorCache cache, Ruby rt, AbstractIRMethod air2, String name2, Block blk2) {
this(args, cache, rt);
air = air2;
name = name2;
blk = blk2;
public SplitCtorData(IRubyObject[] args, JCtorCache cache, Ruby runtime, AbstractIRMethod method, String name, Block block) {
this(args, cache, runtime);
this.method = method;
this.name = name;
blk = block;
}

// fields below are only used in ConcreteJavaProxy finishInitialize
private AbstractIRMethod air = null;
private String name = null;
private SplitSuperState<?> state = null;
AbstractIRMethod method;
String name;
SplitSuperState<?> state;
}

/**
* Used by reified classes, this method is tightly coupled with RealClassGenerator, finishInitialize
* Do not refactor without looking at RCG
* @return An object used by reified code and the finishInitialize method
*/
public SplitCtorData splitInitialized(RubyClass base, IRubyObject[] args, Block blk, JCtorCache jcc) {
String name = base.getClassConfig().javaCtorMethodName;
DynamicMethod dm = base.searchMethod(name);
if (dm != null && (dm instanceof StaticJCreateMethod)) dm = ((StaticJCreateMethod) dm).getOriginal();
DynamicMethod dm1 = base.searchMethodLateral(name); // only on ourself //TODO: missing default
public SplitCtorData splitInitialized(RubyClass base, IRubyObject[] args, Block block, JCtorCache jcc) {
final Ruby runtime = getRuntime();
final String name = base.getClassConfig().javaCtorMethodName;
final CacheEntry methodEntry = base.searchWithCache(name);
final boolean isLateral = isClassOrIncludedPrependedModule(methodEntry.sourceModule, base);
DynamicMethod method = methodEntry.method;
if (method instanceof StaticJCreateMethod) method = ((StaticJCreateMethod) method).oldInit;

// jcreate is for nested ruby classes from a java class
if ((dm1 != null && !(dm instanceof InitializeMethod) && !(dm instanceof StaticJCreateMethod))) {
if (isLateral && method instanceof AbstractIRMethod) {

AbstractIRMethod air = (AbstractIRMethod) dm; // TODO: getMetaClass() ? or base? (below v)
AbstractIRMethod air = (AbstractIRMethod) method; // TODO: getMetaClass() ? or base? (below v)

SplitSuperState<?> state = air.startSplitSuperCall(getRuntime().getCurrentContext(), this, getMetaClass(),
name, args, blk);
SplitSuperState<?> state = air.startSplitSuperCall(runtime.getCurrentContext(), this, getMetaClass(), name, args, block);
if (state == null) { // no super in method
return new SplitCtorData(args, jcc, getRuntime(), air, name, blk);
} else {
return new SplitCtorData(state.callArrayArgs.toJavaArrayMaybeUnsafe(), jcc, getRuntime(), air, state, blk);
return new SplitCtorData(args, jcc, runtime, air, name, block);
}
} else {
return new SplitCtorData(args, jcc, getRuntime());
return new SplitCtorData(state.callArrayArgs.toJavaArrayMaybeUnsafe(), jcc, runtime, air, state, block);
}
return new SplitCtorData(args, jcc, runtime);
}

private static boolean isClassOrIncludedPrependedModule(final RubyModule methodSource, final RubyClass klass) {
if (methodSource == klass) return true;

RubyClass candidate = klass.getSuperClass();
while (candidate != null && (candidate.isIncluded() || candidate.isPrepended())) { // up till 'real' superclass
if (candidate == klass) return true;
}

return false;
}

/**
* Used by reified classes, this method is tightly coupled with RealClassGenerator, splitInitialize
* Do not refactor without looking at RCG
*/
public void finishInitialize(SplitCtorData returned) {
if (returned.air != null) {
if (returned.method != null) {
ThreadContext context = getRuntime().getCurrentContext();
if (returned.state != null) {
returned.air.finishSplitCall(returned.state);
returned.method.finishSplitCall(returned.state);
} else { // no super, direct call
returned.air.call(getRuntime().getCurrentContext(), this, getMetaClass(),
returned.name, returned.rbarguments, returned.blk);
returned.method.call(context, this, getMetaClass(), returned.name, returned.rbarguments, returned.blk);
}
}
// Ignore other cases
Expand Down

0 comments on commit cc1011a

Please sign in to comment.