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.
* all super instructions get rewritten to the appropriate type for
  the target class, instance/class/module

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 cb27869
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 48 deletions.
81 changes: 70 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,17 @@
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.SuperInstr;
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 +112,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 +1627,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 +2397,67 @@ 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 instr : bb.getInstrs()) {
if (instr instanceof SuperInstr) {
SuperInstr superInstr = (SuperInstr) instr;

if (this.isSingleton()) {
instr = new ClassSuperInstr(
closure,
superInstr.getResult(),
superInstr.getDefiningModule(),
name,
superInstr.getCallArgs(),
superInstr.getClosureArg(),
superInstr.isPotentiallyRefined());
} else if (this.isModule()) {
instr = new ModuleSuperInstr(
closure,
superInstr.getResult(),
name,
superInstr.getReceiver(),
superInstr.getCallArgs(),
superInstr.getClosureArg(),
superInstr.isPotentiallyRefined());
} else {
instr = new InstanceSuperInstr(
closure,
superInstr.getResult(),
superInstr.getDefiningModule(),
name,
superInstr.getCallArgs(),
superInstr.getClosureArg(),
superInstr.isPotentiallyRefined());
}
}

newList.add(instr);
}
}

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
32 changes: 24 additions & 8 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());
superInstr = new UnresolvedSuperInstr(scope, getCurrentModuleVariable(), ret, buildSelf(), args, block, scope.maybeUsingRefinements());
}

receiveBreakException(block, superInstr);
return ret;
}
Expand Down Expand Up @@ -4254,7 +4270,7 @@ private Operand buildZSuperIfNest(final Operand block) {
Variable zsuperResult = createTemporaryVariable();
if (superScope instanceof IRMethod && !defineMethod) {
Operand[] args = adjustVariableDepth(getCallArgs(superScope, superBuilder), depthFromSuper);
addInstr(new ZSuperInstr(scope, zsuperResult, buildSelf(), args, block, scope.maybeUsingRefinements()));
addInstr(new ZSuperInstr(scope, zsuperResult, getCurrentModuleVariable(), buildSelf(), args, block, scope.maybeUsingRefinements()));
} else {
// We will not have a zsuper show up since we won't emit it but we still need to toggle it.
// define_method optimization will try and create a method from a closure but it should not in this case.
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@

import java.util.EnumSet;

public class ClassSuperInstr extends CallInstr {
public class ClassSuperInstr extends SuperInstr {
private final boolean isLiteralBlock;

// clone constructor
protected ClassSuperInstr(IRScope scope, Variable result, Operand receiver, RubySymbol name, Operand[] args,
Operand closure, boolean potentiallyRefined, CallSite callSite, long callSiteId) {
super(scope, Operation.CLASS_SUPER, CallType.SUPER, result, name, receiver, args, closure, potentiallyRefined, callSite, callSiteId);
super(scope, Operation.CLASS_SUPER, result, receiver, name, args, closure, potentiallyRefined, callSite, callSiteId);

isLiteralBlock = closure instanceof WrappedIRClosure;
}

// normal constructor
public ClassSuperInstr(IRScope scope, Variable result, Operand definingModule, RubySymbol name, Operand[] args, Operand closure,
boolean isPotentiallyRefined) {
super(scope, Operation.CLASS_SUPER, CallType.SUPER, result, name, definingModule, args, closure, isPotentiallyRefined);
super(scope, Operation.CLASS_SUPER, result, definingModule, name, args, closure, isPotentiallyRefined);

isLiteralBlock = closure instanceof WrappedIRClosure;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@

import java.util.EnumSet;

public class InstanceSuperInstr extends CallInstr {
public class InstanceSuperInstr extends SuperInstr {
private final boolean isLiteralBlock;

// clone constructor
protected InstanceSuperInstr(IRScope scope, Variable result, Operand definingModule, RubySymbol name, Operand[] args,
Operand closure, boolean isPotentiallyRefined, CallSite callSite, long callSiteId) {
super(scope, Operation.INSTANCE_SUPER, CallType.SUPER, result, name, definingModule, args, closure,
super(scope, Operation.INSTANCE_SUPER, result, definingModule, name, args, closure,
isPotentiallyRefined, callSite, callSiteId);

isLiteralBlock = closure instanceof WrappedIRClosure;
Expand All @@ -64,7 +64,7 @@ protected InstanceSuperInstr(IRScope scope, Variable result, Operand definingMod
// normal constructor
public InstanceSuperInstr(IRScope scope, Variable result, Operand definingModule, RubySymbol name, Operand[] args,
Operand closure, boolean isPotentiallyRefined) {
super(scope, Operation.INSTANCE_SUPER, CallType.SUPER, result, name, definingModule, args, closure, isPotentiallyRefined);
super(scope, Operation.INSTANCE_SUPER, result, definingModule, name, args, closure, isPotentiallyRefined);

isLiteralBlock = closure instanceof WrappedIRClosure;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,21 @@

// SSS FIXME: receiver is never used -- being passed in only to meet requirements of CallInstr

public class ModuleSuperInstr extends CallInstr {
public class ModuleSuperInstr extends SuperInstr {
private final boolean isLiteralBlock;

// clone constructor
public ModuleSuperInstr(IRScope scope, Operation op, Variable result, RubySymbol name, Operand receiver, Operand[] args,
Operand closure, boolean isPotentiallyRefined, CallSite callSite, long callSiteId) {
super(scope, op, CallType.SUPER, result, name,
receiver, args, closure, isPotentiallyRefined, callSite, callSiteId);
super(scope, op, result, receiver, name, args, closure, isPotentiallyRefined, callSite, callSiteId);

isLiteralBlock = closure instanceof WrappedIRClosure;
}

// normal constructor
public ModuleSuperInstr(IRScope scope, Operation op, Variable result, RubySymbol name, Operand receiver, Operand[] args, Operand closure,
boolean isPotentiallyRefined) {
super(scope, op, CallType.SUPER, result, name,
receiver, args, closure, isPotentiallyRefined);
super(scope, op, result, receiver, name, args, closure, isPotentiallyRefined);

isLiteralBlock = closure instanceof WrappedIRClosure;
}
Expand All @@ -53,6 +51,11 @@ public ModuleSuperInstr(IRScope scope, Variable result, RubySymbol name, Operand
this(scope, Operation.MODULE_SUPER, result, name, receiver, args, closure, isPotentiallyRefined);
}

@Override
public Operand getDefiningModule() {
return getReceiver();
}

@Override
public boolean computeScopeFlags(IRScope scope, EnumSet<IRFlags> flags) {
super.computeScopeFlags(scope, flags);
Expand Down
45 changes: 45 additions & 0 deletions core/src/main/java/org/jruby/ir/instructions/SuperInstr.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.jruby.ir.instructions;

import org.jruby.RubyInstanceConfig;
import org.jruby.RubySymbol;
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRScope;
import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.operands.WrappedIRClosure;
import org.jruby.ir.persistence.IRReaderDecoder;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.CallType;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import java.util.EnumSet;

public abstract class SuperInstr extends CallInstr {
private final boolean isLiteralBlock;

// clone constructor
protected SuperInstr(IRScope scope, Operation op, Variable result, Operand receiver, RubySymbol name, Operand[] args,
Operand closure, boolean potentiallyRefined, CallSite callSite, long callSiteId) {
super(scope, op, CallType.SUPER, result, name, receiver, args, closure, potentiallyRefined, callSite, callSiteId);

isLiteralBlock = closure instanceof WrappedIRClosure;
}

// normal constructor
public SuperInstr(IRScope scope, Operation op, Variable result, Operand definingModule, RubySymbol name, Operand[] args, Operand closure,
boolean isPotentiallyRefined) {
super(scope, op, CallType.SUPER, result, name, definingModule, args, closure, isPotentiallyRefined);

isLiteralBlock = closure instanceof WrappedIRClosure;
}

public abstract Operand getDefiningModule();
}

0 comments on commit cb27869

Please sign in to comment.