Skip to content

Commit

Permalink
Always use ModuleSuper and rewrite define_method
Browse files Browse the repository at this point in the history
The changes here attempt to get more super calls using a static
name, so we can eliminate dependency on a call frame to get that
name.

* Super in a block in any method will start out as a ModuleSuper
  using the name from the method.
* define_method clones and rewrites the block to retarget any
  super calls to the newly defined name.

Remaining cases that use UnresolvedSuper may only be the ones
where super is invalid, such as a block at top-level or within a
class or module body.
  • Loading branch information
headius committed Feb 16, 2022
1 parent a151aad commit 3e18abe
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 18 deletions.
70 changes: 59 additions & 11 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,16 @@
import org.jruby.internal.runtime.methods.UndefinedMethod;
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRMethod;
import org.jruby.ir.instructions.CallBase;
import org.jruby.ir.instructions.ClassSuperInstr;
import org.jruby.ir.instructions.InstanceSuperInstr;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.ModuleSuperInstr;
import org.jruby.ir.instructions.UnresolvedSuperInstr;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.targets.indy.Bootstrap;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;
import org.jruby.javasupport.JavaClass;
import org.jruby.javasupport.binding.MethodGatherer;
import org.jruby.parser.StaticScope;
Expand All @@ -102,6 +111,7 @@
import org.jruby.runtime.IRBlockBody;
import org.jruby.runtime.MethodFactory;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.MixedModeIRBlockBody;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
Expand Down Expand Up @@ -1616,7 +1626,7 @@ private CacheEntry refinedMethodOriginalMethodEntry(Map<RubyModule, RubyModule>
* failed to return a result. Cache superclass definitions in this class.
*
* MRI: method_entry_get_without_cache
*
*
* @param id The name of the method to search for
* @param cacheUndef Flag for caching UndefinedMethod. This should normally be true.
* @return The method, or UndefinedMethod if not found
Expand Down Expand Up @@ -2386,19 +2396,57 @@ public IRubyObject defineMethodFromBlock(ThreadContext context, IRubyObject arg0

// If we know it comes from IR we can convert this directly to a method and
// avoid overhead of invoking it as a block
if (block.getBody() instanceof IRBlockBody &&
runtime.getInstanceConfig().getCompileMode().shouldJIT()) { // FIXME: Once Interp and Mixed Methods are one class we can fix this to work in interp mode too.
if (block.getBody() instanceof IRBlockBody) {
IRBlockBody body = (IRBlockBody) block.getBody();
IRClosure closure = body.getScope();

// closure may be null from AOT scripts
if (closure != null) {
// Ask closure to give us a method equivalent.
IRMethod method = closure.convertToMethod(name.getBytes());
if (method != null) {
newMethod = new DefineMethodMethod(method, visibility, this, context.getFrameBlock());
Helpers.addInstanceMethod(this, name, newMethod, visibility, context, runtime);
return name;
// clone for rewriting and optimization specific to define_method
closure = closure.cloneForInlining(new SimpleCloneInfo(closure, false));
body = new MixedModeIRBlockBody(closure, body.getSignature());
block = new Block(body, block.getBinding(), block.type);

InterpreterContext ic = closure.prepareFullBuild();
for (BasicBlock bb : ic.getCFG().getBasicBlocks()) {
ArrayList<Instr> newList = new ArrayList<>();
for (Instr i : bb.getInstrs()) {
if (i instanceof CallBase) {
CallBase cb = (CallBase) i;

if (cb instanceof UnresolvedSuperInstr) {
newList.add(
new ModuleSuperInstr(
closure,
cb.getResult(),
name,
cb.getReceiver(),
cb.getCallArgs(),
cb.getClosureArg(),
cb.isPotentiallyRefined()));
continue;
}

if (cb instanceof InstanceSuperInstr ||
cb instanceof ClassSuperInstr ||
cb instanceof ModuleSuperInstr) {
cb.setName(name);
}
}

newList.add(i);
}
}

if (runtime.getInstanceConfig().getCompileMode().shouldJIT()) { // FIXME: Once Interp and Mixed Methods are one class we can fix this to work in interp mode too.

// closure may be null from AOT scripts
if (closure != null) {
// Ask closure to give us a method equivalent.
IRMethod method = closure.convertToMethod(name.getBytes());
if (method != null) {
newMethod = new DefineMethodMethod(method, visibility, this, context.getFrameBlock());
Helpers.addInstanceMethod(this, name, newMethod, visibility, context, runtime);
return name;
}
}
}
}
Expand Down
28 changes: 22 additions & 6 deletions core/src/main/java/org/jruby/ir/IRBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4077,21 +4077,37 @@ public Operand buildStrRaw(StrNode strNode) {
private Operand buildSuperInstr(Operand block, Operand[] args) {
CallInstr superInstr;
Variable ret = createTemporaryVariable();
if (scope instanceof IRMethod && scope.getLexicalParent() instanceof IRClassBody) {
if (((IRMethod) scope).isInstanceMethod) {
superInstr = new InstanceSuperInstr(scope, ret, getCurrentModuleVariable(), getName(), args, block, scope.maybeUsingRefinements());
IRMethod nearestMethod = scope.getNearestMethod();

if (nearestMethod != null) {
IRScope lexicalParent;
RubySymbol superName;

if (scope instanceof IRMethod) {
lexicalParent = scope.getLexicalParent();
superName = getName();
} else {
superInstr = new ClassSuperInstr(scope, ret, getCurrentModuleVariable(), getName(), args, block, scope.maybeUsingRefinements());
lexicalParent = nearestMethod.getLexicalParent();
superName = nearestMethod.getName();
}

if (lexicalParent instanceof IRClassBody) {
if (nearestMethod.isInstanceMethod) {
superInstr = new InstanceSuperInstr(scope, ret, getCurrentModuleVariable(), superName, args, block, scope.maybeUsingRefinements());
} else {
superInstr = new ClassSuperInstr(scope, ret, getCurrentModuleVariable(), superName, args, block, scope.maybeUsingRefinements());
}
} else {
superInstr = new ModuleSuperInstr(scope, ret, superName, buildSelf(), args, block, scope.maybeUsingRefinements());
}
} else if (scope instanceof IRMethod && scope.getLexicalParent() instanceof IRModuleBody) {
superInstr = new ModuleSuperInstr(scope, ret, getName(), buildSelf(), args, block, scope.maybeUsingRefinements());
} else {
// We dont always know the method name we are going to be invoking if the super occurs in a closure.
// This is because the super can be part of a block that will be used by 'define_method' to define
// a new method. In that case, the method called by super will be determined by the 'name' argument
// to 'define_method'.
superInstr = new UnresolvedSuperInstr(scope, ret, buildSelf(), args, block, scope.maybeUsingRefinements());
}

receiveBreakException(block, superInstr);
return ret;
}
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/java/org/jruby/ir/instructions/CallBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public abstract class CallBase extends NOperandInstr implements ClosureAccepting

public transient long callSiteId;
private final CallType callType;
protected final RubySymbol name;
protected RubySymbol name;
protected final transient CallSite callSite;
protected final transient int argsCount;
protected final transient boolean hasClosure;
Expand Down Expand Up @@ -104,6 +104,10 @@ public String getId() {
return name.idString();
}

public void setName(RubySymbol name) {
this.name = name;
}

public long getCallSiteId() {
return callSiteId;
}
Expand Down

0 comments on commit 3e18abe

Please sign in to comment.