package org.jruby.ir;
import org.jruby.ParseResult;
import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.RubySymbol;
import org.jruby.compiler.Compilable;
import org.jruby.ir.instructions.*;
import org.jruby.ir.interpreter.FullInterpreterContext;
import org.jruby.ir.interpreter.InterpreterContext;
import org.jruby.ir.operands.*;
import org.jruby.ir.passes.*;
import org.jruby.ir.persistence.IRWriterEncoder;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.transformations.inlining.CFGInliner;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;
import org.jruby.ir.util.IGVDumper;
import org.jruby.parser.StaticScope;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.jruby.runtime.ThreadContext;
import org.jruby.util.ByteList;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import static org.jruby.ir.IRFlags.*;
public abstract class IRScope implements ParseResult {
public static final Logger LOG = LoggerFactory.getLogger(IRScope.class);
private static final Collection<IRClosure> NO_CLOSURES = Collections.EMPTY_LIST;
private static final AtomicInteger globalScopeCount = new AtomicInteger();
private final int scopeId;
private final int lineNumber;
private final IRScope lexicalParent;
private final StaticScope staticScope;
private final IRManager manager;
private ByteList name;
private List<IRClosure> nestedClosures;
private int nextClosureIndex;
private List<IRScope> lexicalChildren;
protected InterpreterContext interpreterContext;
protected FullInterpreterContext fullInterpreterContext;
protected FullInterpreterContext optimizedInterpreterContext;
private int nextLabelIndex = 0;
Map<RubySymbol, LocalVariable> localVars;
private boolean alreadyHasInline;
private String inlineFailed;
public Compilable compilable;
private boolean hasBreakInstructions;
private boolean hasLoops;
private boolean hasNonLocalReturns;
private boolean receivesClosureArg;
private boolean receivesKeywordArgs;
private boolean accessesParentsLocalVariables;
private boolean maybeUsingRefinements;
private boolean canCaptureCallersBinding;
private boolean canReceiveBreaks;
private boolean canReceiveNonLocalReturns;
private boolean usesZSuper;
private boolean needsCodeCoverage;
private boolean usesEval;
protected IRScope(IRScope s, IRScope lexicalParent) {
this.lexicalParent = lexicalParent;
this.manager = s.manager;
this.lineNumber = s.lineNumber;
this.staticScope = s.staticScope;
this.nextClosureIndex = s.nextClosureIndex;
this.interpreterContext = null;
this.localVars = new HashMap<>(s.localVars);
this.scopeId = globalScopeCount.getAndIncrement();
setupLexicalContainment();
}
public IRScope(IRManager manager, IRScope lexicalParent, ByteList name, int lineNumber, StaticScope staticScope) {
this.manager = manager;
this.lexicalParent = lexicalParent;
this.name = name;
this.lineNumber = lineNumber;
this.staticScope = staticScope;
this.nextClosureIndex = 0;
this.interpreterContext = null;
if (parentMaybeUsingRefinements()) setIsMaybeUsingRefinements();
this.localVars = new HashMap<>(1);
this.scopeId = globalScopeCount.getAndIncrement();
setupLexicalContainment();
}
private void setupLexicalContainment() {
if (manager.isDryRun() || RubyInstanceConfig.IR_WRITING || RubyInstanceConfig.RECORD_LEXICAL_HIERARCHY) {
lexicalChildren = new ArrayList<>(1);
if (lexicalParent != null) lexicalParent.addChildScope(this);
}
}
public int getScopeId() {
return scopeId;
}
@Override
public int hashCode() {
return scopeId;
}
@Override
public boolean equals(Object other) {
return (other != null) && (getClass() == other.getClass()) && (scopeId == ((IRScope) other).scopeId);
}
protected void addChildScope(IRScope scope) {
if (lexicalChildren == null) lexicalChildren = new ArrayList<>(1);
lexicalChildren.add(scope);
}
public List<IRScope> getLexicalScopes() {
if (lexicalChildren == null) lexicalChildren = new ArrayList<>(1);
return lexicalChildren;
}
public void addClosure(IRClosure closure) {
if (nestedClosures == null) nestedClosures = new ArrayList<>(1);
nestedClosures.add(closure);
}
public void removeClosure(IRClosure closure) {
if (nestedClosures != null) nestedClosures.remove(closure);
}
public Label getNewLabel(String prefix) {
return new Label(prefix, nextLabelIndex++);
}
public Label getNewLabel() {
return getNewLabel("LBL");
}
public Collection<IRClosure> getClosures() {
return nestedClosures == null ? NO_CLOSURES : nestedClosures;
}
public IRManager getManager() {
return manager;
}
public void setIsMaybeUsingRefinements() {
maybeUsingRefinements = true;
}
public boolean parentMaybeUsingRefinements() {
for (IRScope s = this; s != null; s = s.getLexicalParent()) {
if (s.maybeUsingRefinements()) return true;
if (s instanceof IREvalScript) return false;
}
return false;
}
public boolean maybeUsingRefinements() {
return maybeUsingRefinements;
}
public IRScope getLexicalParent() {
return lexicalParent;
}
public StaticScope getStaticScope() {
return staticScope;
}
public boolean isWithinEND() {
for (IRScope current = this; current != null && current instanceof IRClosure; current = current.getLexicalParent()) {
if (((IRClosure) current).isEND()) return true;
}
return false;
}
public IRMethod getNearestMethod() {
IRScope current = this;
while (current != null && !(current instanceof IRMethod)) {
current = current.getLexicalParent();
}
return (IRMethod) current;
}
public IRScope getNearestTopLocalVariableScope() {
IRScope current = this;
while (current != null && !current.isTopLocalVariableScope()) {
current = current.getLexicalParent();
}
return current;
}
public boolean isScopeContainedBy(IRScope parentScope) {
IRScope current = this;
while (current != null) {
if (parentScope == current) return true;
current = current.getLexicalParent();
}
return false;
}
public int getNearestModuleReferencingScopeDepth() {
int n = 0;
IRScope current = this;
while (!(current instanceof IRModuleBody)) {
if (current == null || current instanceof IREvalScript) return -1;
current = current.getLexicalParent();
if (!(current instanceof IRFor)) n++;
}
return n;
}
public String getId() {
return getName().idString();
}
public RubySymbol getName() {
return getManager().getRuntime().newSymbol(name);
}
public ByteList getByteName() {
return name;
}
public void setByteName(ByteList name) {
this.name = name;
}
public void setFileName(String filename) {
getRootLexicalScope().setFileName(filename);
}
@Deprecated
public String getFileName() {
return getFile();
}
public String getFile() {
return getRootLexicalScope().getFile();
}
@Deprecated
public int getLineNumber() {
return lineNumber;
}
public int getLine() {
return lineNumber;
}
public IRScope getRootLexicalScope() {
IRScope current = this;
for (; current != null && !current.isScriptScope(); current = current.getLexicalParent()) {}
return current;
}
public boolean isNestedInClosure(IRClosure closure) {
for (IRScope s = this; s != null && !s.isTopLocalVariableScope(); s = s.getLexicalParent()) {
if (s == closure) return true;
}
return false;
}
public void setHasBreakInstructions() {
hasBreakInstructions = true;
}
public boolean hasBreakInstructions() {
return hasBreakInstructions;
}
public void setReceivesKeywordArgs() {
receivesKeywordArgs = true;
}
public boolean receivesKeywordArgs() {
return receivesKeywordArgs;
}
public void setReceivesClosureArg() {
receivesClosureArg = true;
}
public boolean receivesClosureArg() {
return receivesClosureArg;
}
public void setAccessesParentsLocalVariables() {
accessesParentsLocalVariables = true;
}
public boolean accessesParentsLocalVariables() {
return accessesParentsLocalVariables;
}
public void setHasLoops() {
hasLoops = true;
}
public boolean hasLoops() {
return hasLoops;
}
public void setHasNonLocalReturns() {
hasNonLocalReturns = true;
}
public boolean hasNonLocalReturns() {
return hasNonLocalReturns;
}
public void setCanCaptureCallersBinding() {
canCaptureCallersBinding = true;
}
public boolean canCaptureCallersBinding() {
return canCaptureCallersBinding;
}
public void setCanReceiveBreaks() {
canReceiveBreaks = true;
}
public boolean canReceiveBreaks() {
return canReceiveBreaks;
}
public void setCanReceiveNonlocalReturns() {
canReceiveNonLocalReturns = true;
}
public boolean canReceiveNonlocalReturns() {
return canReceiveNonLocalReturns;
}
public void setUsesEval() {
usesEval = true;
}
public boolean usesEval() {
return usesEval;
}
public void setUsesZSuper() {
usesZSuper = true;
}
public boolean usesZSuper() {
return usesZSuper;
}
public void setNeedsCodeCoverage() {
needsCodeCoverage = true;
}
public boolean needsCodeCoverage() {
return needsCodeCoverage;
}
public List<CompilerPass> getExecutedPasses() {
return fullInterpreterContext == null ? new ArrayList<CompilerPass>(1) : fullInterpreterContext.getExecutedPasses();
}
private void runCompilerPasses(FullInterpreterContext fic, List<CompilerPass> passes, IGVDumper dumper) {
if (dumper != null) dumper.dump(fic.getCFG(), "Start");
CompilerPassScheduler scheduler = IRManager.schedulePasses(passes);
for (CompilerPass pass : scheduler) {
pass.run(fic);
if (dumper != null) dumper.dump(fic.getCFG(), pass.getShortLabel());
}
if (RubyInstanceConfig.IR_UNBOXING) {
CompilerPass pass = new UnboxingPass();
pass.run(fic);
if (dumper != null) dumper.dump(fic.getCFG(), pass.getShortLabel());
}
if (dumper != null) dumper.close();
}
public InterpreterContext allocateInterpreterContext(List<Instr> instructions, int tempVariableCount, EnumSet<IRFlags> flags) {
interpreterContext = new InterpreterContext(this, instructions, tempVariableCount, flags);
if (RubyInstanceConfig.IR_COMPILER_DEBUG) LOG.info(interpreterContext.toString());
return interpreterContext;
}
public InterpreterContext allocateInterpreterContext(Supplier<List<Instr>> instructions, int tempVariableCount, EnumSet<IRFlags> flags) {
interpreterContext = new InterpreterContext(this, instructions, tempVariableCount, flags);
if (RubyInstanceConfig.IR_COMPILER_DEBUG) LOG.info(interpreterContext.toString());
return interpreterContext;
}
private Instr[] cloneInstrs() {
SimpleCloneInfo cloneInfo = new SimpleCloneInfo(this, false);
Instr[] instructions = interpreterContext.getInstructions();
int length = instructions.length;
Instr[] newInstructions = new Instr[length];
for (int i = 0; i < length; i++) {
newInstructions[i] = instructions[i].clone(cloneInfo);
}
return newInstructions;
}
public synchronized FullInterpreterContext prepareFullBuild() {
if (optimizedInterpreterContext != null) return optimizedInterpreterContext;
if (fullInterpreterContext != null) return fullInterpreterContext;
for (IRScope scope: getClosures()) {
scope.prepareFullBuild();
}
FullInterpreterContext fic = new FullInterpreterContext(this, cloneInstrs(), interpreterContext.getTemporaryVariableCount(), interpreterContext.getFlags().clone());
runCompilerPasses(fic, getManager().getCompilerPasses(this), dumpToIGV());
getManager().optimizeIfSimpleScope(fic);
new AddCallProtocolInstructions().run(fic);
fic.generateInstructionsForInterpretation();
this.fullInterpreterContext = fic;
return fic;
}
public String getFullyQualifiedName() {
if (getLexicalParent() == null) return getId();
return getLexicalParent().getFullyQualifiedName() + "::" + getId();
}
public IGVDumper dumpToIGV() {
if (RubyInstanceConfig.IR_DEBUG_IGV != null) {
String spec = RubyInstanceConfig.IR_DEBUG_IGV;
if (spec.contains(":") && spec.equals(getFileName() + ":" + getLineNumber()) ||
spec.equals(getFileName())) {
return new IGVDumper(getFullyQualifiedName() + "; line " + getLineNumber());
}
}
return null;
}
public synchronized BasicBlock[] prepareForCompilation() {
if (optimizedInterpreterContext != null) return optimizedInterpreterContext.getLinearizedBBList();
if (fullInterpreterContext != null) return fullInterpreterContext.getLinearizedBBList();
for (IRScope scope: getClosures()) {
scope.prepareForCompilation();
}
FullInterpreterContext fic = new FullInterpreterContext(this, cloneInstrs(), interpreterContext.getTemporaryVariableCount(), interpreterContext.getFlags().clone());
runCompilerPasses(fic, getManager().getJITPasses(this), dumpToIGV());
BasicBlock[] bbs = fic.linearizeBasicBlocks();
this.fullInterpreterContext = fic;
return bbs;
}
public Map<BasicBlock, Label> buildJVMExceptionTable(FullInterpreterContext fic) {
Map<BasicBlock, Label> map = new HashMap<>(1);
for (BasicBlock bb: fic.getLinearizedBBList()) {
BasicBlock rescueBB = fic.getCFG().getRescuerBBFor(bb);
if (rescueBB != null) {
map.put(bb, rescueBB.getLabel());
}
}
return map;
}
public abstract IRScopeType getScopeType();
@Override
public String toString() {
return String.valueOf(getScopeType()) + ' ' + getId() + '[' + getFile() + ':' + getLine() + "]<" + toStringCompileForm() + ">";
}
public String toStringCompileForm() {
return optimizedInterpreterContext != null ? "optimized" : fullInterpreterContext != null ? "full" : "startup";
}
public String debugOutput() {
return toStringInstrs();
}
public String toStringInstrs() {
if (fullInterpreterContext != null) {
return "Instructions:\n" + fullInterpreterContext.toStringInstrs();
} else {
return interpreterContext.toStringInstrs();
}
}
public Variable getSelf() {
return Self.SELF;
}
public Map<RubySymbol, LocalVariable> getLocalVariables() {
return localVars;
}
public void setNextLabelIndex(int index) {
nextLabelIndex = index;
}
public int getNextLabelIndex() {
return nextLabelIndex;
}
public LocalVariable lookupExistingLVar(RubySymbol name) {
return localVars.get(name);
}
protected LocalVariable findExistingLocalVariable(RubySymbol name, int depth) {
return localVars.get(name);
}
public LocalVariable getLocalVariable(RubySymbol name, int scopeDepth) {
LocalVariable lvar = findExistingLocalVariable(name, scopeDepth);
if (lvar == null) {
lvar = getNewLocalVariable(name, scopeDepth);
} else if (lvar.getScopeDepth() != scopeDepth) {
lvar = lvar.cloneForDepth(scopeDepth);
}
return lvar;
}
public LocalVariable getNewLocalVariable(RubySymbol name, int scopeDepth) {
assert scopeDepth == 0: "Scope depth is non-zero for new-var request " + name + " in " + this;
LocalVariable lvar = new LocalVariable(name, scopeDepth, getStaticScope().addVariable(name.idString()));
localVars.put(name, lvar);
return lvar;
}
public boolean hasBeenBuilt() {
return true;
}
public FullInterpreterContext getExecutionContext() {
return fullInterpreterContext;
}
public InterpreterContext getInterpreterContext() {
return interpreterContext;
}
public FullInterpreterContext getFullInterpreterContext() {
return fullInterpreterContext;
}
public FullInterpreterContext getOptimizedInterpreterContext() {
return optimizedInterpreterContext;
}
protected void depends(Object obj) {
assert obj != null: "Unsatisfied dependency and this depends() was set " +
"up wrong. Use depends(build()) not depends(build).";
}
private FullInterpreterContext inlineFailed(String reason) {
inlineFailed = reason;
return null;
}
private FullInterpreterContext inlineMethodCommon(IRMethod methodToInline, RubyModule implClass, long callsiteId, int classToken, boolean cloneHost) {
alreadyHasInline = true;
if (getFullInterpreterContext() == null) prepareFullBuild();
if (!methodToInline.getClosures().isEmpty()) {
boolean accessInaccessibleLocalVariables = false;
for (IRClosure closure: methodToInline.getClosures()) {
if (closure.accessesParentsLocalVariables()) {
accessInaccessibleLocalVariables = true;
break;
}
}
if (accessInaccessibleLocalVariables) return inlineFailed("inline a method which contains nested closures which access methods lvars");
}
FullInterpreterContext newContext = getFullInterpreterContext().duplicate();
if (newContext == null) {
return inlineFailed("FIXME: BBs are not linearized???");
}
BasicBlock basicBlock = newContext.findBasicBlockOf(callsiteId);
CallBase call = (CallBase) basicBlock.siteOf(callsiteId);
String error = new CFGInliner(newContext).inlineMethod(methodToInline, implClass, classToken, basicBlock, call, cloneHost);
return error == null ? newContext : inlineFailed(error);
}
public void inlineMethod(IRMethod methodToInline, RubyModule metaclass, long callsiteId, int classToken, boolean cloneHost) {
if (alreadyHasInline) return;
FullInterpreterContext newContext = inlineMethodCommon(methodToInline, metaclass, callsiteId, classToken, cloneHost);
if (newContext == null) {
if (IRManager.IR_INLINER_VERBOSE) LOG.info("Inline of " + methodToInline + " into " + this + " failed: " + inlineFailed + ".");
return;
} else {
if (IRManager.IR_INLINER_VERBOSE) LOG.info("Inline of " + methodToInline + " into " + this + " succeeded.");
}
newContext.generateInstructionsForInterpretation();
this.optimizedInterpreterContext = newContext;
manager.getRuntime().getJITCompiler().getTaskFor(manager.getRuntime().getCurrentContext(), compilable).run();
}
public void inlineMethodJIT(IRMethod methodToInline, RubyModule implClass, long callsiteId, int classToken, boolean cloneHost) {
if (alreadyHasInline) return;
FullInterpreterContext newContext = inlineMethodCommon(methodToInline, implClass, callsiteId, classToken, cloneHost);
Ruby runtime = manager.getRuntime();
if (newContext == null) {
if (IRManager.IR_INLINER_VERBOSE) LOG.info("Inline of " + methodToInline + " into " + this + " failed: " + inlineFailed + ".");
runtime.getInlineStats().incrementInlineFailedCount();
return;
} else {
if (IRManager.IR_INLINER_VERBOSE) LOG.info("Inline of " + methodToInline + " into " + this + " succeeded.");
runtime.getInlineStats().incrementInlineSuccessCount();
}
newContext.linearizeBasicBlocks();
this.optimizedInterpreterContext = newContext;
runtime.getJITCompiler().getTaskFor(manager.getRuntime().getCurrentContext(), compilable).run();
}
public void inlineMethodCompiled(IRMethod methodToInline, RubyModule implClass, long callsiteId, int classToken, boolean cloneHost) {
if (alreadyHasInline) return;
FullInterpreterContext newContext = inlineMethodCommon(methodToInline, implClass, callsiteId, classToken, cloneHost);
if (newContext == null) {
if (IRManager.IR_INLINER_VERBOSE) LOG.info("Inline of " + methodToInline + " into " + this + " failed: " + inlineFailed + ".");
return;
} else {
if (IRManager.IR_INLINER_VERBOSE) LOG.info("Inline of " + methodToInline + " into " + this + " succeeded.");
}
newContext.linearizeBasicBlocks();
this.optimizedInterpreterContext = newContext;
manager.getRuntime().getJITCompiler().getTaskFor(manager.getRuntime().getCurrentContext(), compilable).run();
}
public int getNextClosureId() {
nextClosureIndex++;
return nextClosureIndex;
}
public boolean isModuleBody() {
return false;
}
public boolean isNonSingletonClassBody() {
return false;
}
public boolean isTopLocalVariableScope() {
return true;
}
public boolean isScriptScope() {
return false;
}
public boolean inliningAllowed() {
return !alreadyHasInline;
}
public void captureParentRefinements(ThreadContext context) {
if (maybeUsingRefinements()) {
for (IRScope cur = this.getLexicalParent(); cur != null; cur = cur.getLexicalParent()) {
RubyModule overlay = cur.staticScope.getOverlayModuleForRead();
if (overlay != null && !overlay.getRefinements().isEmpty()) {
RubyModule myOverlay = staticScope.getOverlayModuleForWrite(context);
myOverlay.getRefinementsForWrite().putAll(overlay.getRefinements());
break;
}
}
}
}
public void cleanupAfterExecution() {
}
public boolean executesOnce() {
return false;
}
public void (IRWriterEncoder file) {
if (RubyInstanceConfig.IR_WRITING_DEBUG) System.out.println("IRScopeType = " + getScopeType());
file.encode(getScopeType());
if (RubyInstanceConfig.IR_WRITING_DEBUG) System.out.println("Line # = " + getLine());
file.encode(getLine());
if (RubyInstanceConfig.IR_WRITING_DEBUG) System.out.println("# of temp vars = " + getInterpreterContext().getTemporaryVariableCount());
file.encode(getInterpreterContext().getTemporaryVariableCount());
file.encode(getNextLabelIndex());
}
public void persistScopeFlags(IRWriterEncoder file) {
file.encode(getInterpreterContext().getFlags());
file.encode(hasBreakInstructions());
file.encode(hasLoops());
file.encode(hasNonLocalReturns());
file.encode(receivesClosureArg());
file.encode(receivesKeywordArgs());
file.encode(accessesParentsLocalVariables());
file.encode(maybeUsingRefinements());
file.encode(canCaptureCallersBinding());
file.encode(canReceiveBreaks());
file.encode(canReceiveNonlocalReturns());
file.encode(usesZSuper());
file.encode(needsCodeCoverage());
file.encode(usesEval());
}
public static EnumSet<IRFlags> allocateInitialFlags(IRScope scope) {
if (scope instanceof IREvalScript || scope instanceof IRScriptBody) {
return EnumSet.of(BINDING_HAS_ESCAPED);
} else {
return EnumSet.noneOf(IRFlags.class);
}
}
}