org.jruby.ir.IRScope Maven / Gradle / Ivy
package org.jruby.ir;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.jruby.RubyModule;
import org.jruby.exceptions.Unrescuable;
import org.jruby.ir.dataflow.DataFlowProblem;
import org.jruby.ir.instructions.BreakInstr;
import org.jruby.ir.instructions.CallBase;
import org.jruby.ir.instructions.CopyInstr;
import org.jruby.ir.instructions.DefineMetaClassInstr;
import org.jruby.ir.instructions.GetGlobalVariableInstr;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.NonlocalReturnInstr;
import org.jruby.ir.instructions.PutGlobalVarInstr;
import org.jruby.ir.instructions.ReceiveSelfInstr;
import org.jruby.ir.instructions.ResultInstr;
import org.jruby.ir.instructions.Specializeable;
import org.jruby.ir.instructions.ThreadPollInstr;
import org.jruby.ir.instructions.ruby20.ReceiveKeywordArgInstr;
import org.jruby.ir.instructions.ruby20.ReceiveKeywordRestArgInstr;
import org.jruby.ir.listeners.IRScopeListener;
import org.jruby.ir.operands.GlobalVariable;
import org.jruby.ir.operands.Label;
import org.jruby.ir.operands.LocalVariable;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Self;
import org.jruby.ir.operands.TemporaryVariable;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.passes.CompilerPass;
import org.jruby.ir.passes.CompilerPassScheduler;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.representations.CFG;
import org.jruby.ir.representations.CFGLinearizer;
import org.jruby.ir.transformations.inlining.CFGInliner;
import org.jruby.parser.StaticScope;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
/**
* Right now, this class abstracts the following execution scopes:
* Method, Closure, Module, Class, MetaClass
* Top-level Script, and Eval Script
*
* In the compiler-land, IR versions of these scopes encapsulate only as much
* information as is required to convert Ruby code into equivalent Java code.
*
* But, in the non-compiler land, there will be a corresponding java object for
* some of these scopes which encapsulates the runtime semantics and data needed
* for implementing them. In the case of Module, Class, MetaClass, and Method,
* they also happen to be instances of the corresponding Ruby classes -- so,
* in addition to providing code that help with this specific ruby implementation,
* they also have code that let them behave as ruby instances of their corresponding
* classes.
*
* Examples:
* - the runtime class object might have refs. to the runtime method objects.
* - the runtime method object might have a slot for a heap frame (for when it
* has closures that need access to the method's local variables), it might
* have version information, it might have references to other methods that
* were optimized with the current version number, etc.
* - the runtime closure object will have a slot for a heap frame (for when it
* has closures within) and might get reified as a method in the java land
* (but inaccessible in ruby land). So, passing closures in Java land might
* be equivalent to passing around the method handles.
*
* and so on ...
*/
public abstract class IRScope {
private static final Logger LOG = LoggerFactory.getLogger("IRScope");
private static Integer globalScopeCount = 0;
/** Unique global scope id */
private int scopeId;
/** Name */
private String name;
/** File within which this scope has been defined */
private final String fileName;
/** Starting line for this scope's definition */
private final int lineNumber;
/** Lexical parent scope */
private IRScope lexicalParent;
/** Parser static-scope that this IR scope corresponds to */
private StaticScope staticScope;
/** Live version of module within whose context this method executes */
private RubyModule containerModule;
/** List of IR instructions for this method */
private List instrList;
/** Control flow graph representation of this method's instructions */
private CFG cfg;
/** List of (nested) closures in this scope */
private List nestedClosures;
/** Local variables defined in this scope */
private Set definedLocalVars;
/** Local variables used in this scope */
private Set usedLocalVars;
/** Is %block implicit block arg unused? */
private boolean hasUnusedImplicitBlockArg;
/** %current_module and %current_scope variables */
private TemporaryVariable currentModuleVar;
private TemporaryVariable currentScopeVar;
/** Map of name -> dataflow problem */
private Map dfProbs;
private Instr[] linearizedInstrArray;
private List linearizedBBList;
protected int temporaryVariableIndex;
/** Keeps track of types of prefix indexes for variables and labels */
private Map nextVarIndex;
// Index values to guarantee we don't assign same internal index twice
private int nextClosureIndex;
// List of all scopes this scope contains lexically. This is not used
// for execution, but is used during dry-runs for debugging.
List lexicalChildren;
protected static class LocalVariableAllocator {
public int nextSlot;
public Map varMap;
public LocalVariableAllocator() {
varMap = new HashMap();
nextSlot = 0;
}
public final LocalVariable getVariable(String name) {
return varMap.get(name);
}
public final void putVariable(String name, LocalVariable var) {
varMap.put(name, var);
nextSlot++;
}
}
LocalVariableAllocator localVars;
LocalVariableAllocator evalScopeVars;
/** Have scope flags been computed? */
private boolean flagsComputed;
/* *****************************************************************************************************
* Does this execution scope (applicable only to methods) receive a block and use it in such a way that
* all of the caller's local variables need to be materialized into a heap binding?
* Ex:
* def foo(&b)
* eval 'puts a', b
* end
*
* def bar
* a = 1
* foo {} # prints out '1'
* end
*
* Here, 'foo' can access all of bar's variables because it captures the caller's closure.
*
* There are 2 scenarios when this can happen (even this is conservative -- but, good enough for now)
* 1. This method receives an explicit block argument (in this case, the block can be stored, passed around,
* eval'ed against, called, etc.).
* CAVEAT: This is conservative ... it may not actually be stored & passed around, evaled, called, ...
* 2. This method has a 'super' call (ZSuper AST node -- ZSuperInstr IR instruction)
* In this case, the parent (in the inheritance hierarchy) can access the block and store it, etc. So, in reality,
* rather than assume that the parent will always do this, we can query the parent, if we can precisely identify
* the parent method (which in the face of Ruby's dynamic hierarchy, we cannot). So, be pessimistic.
*
* This logic was extracted from an email thread on the JRuby mailing list -- Yehuda Katz & Charles Nutter
* contributed this analysis above.
* ********************************************************************************************************/
private boolean canCaptureCallersBinding;
/* ****************************************************************************
* Does this scope define code, i.e. does it (or anybody in the downward call chain)
* do class_eval, module_eval? In the absence of any other information, we default
* to yes -- which basically leads to pessimistic but safe optimizations. But, for
* library and internal methods, this might be false.
* **************************************************************************** */
private boolean canModifyCode;
/* ****************************************************************************
* Does this scope require a binding to be materialized?
* Yes if any of the following holds true:
* - calls 'Proc.new'
* - calls 'eval'
* - calls 'call' (could be a call on a stored block which could be local!)
* - calls 'send' and we cannot resolve the message (method name) that is being sent!
* - calls methods that can access the caller's binding
* - calls a method which we cannot resolve now!
* - has a call whose closure requires a binding
* **************************************************************************** */
private boolean bindingHasEscaped;
/** Does this scope call any eval */
private boolean usesEval;
/** Does this scope receive keyword args? */
private boolean receivesKeywordArgs;
/** Does this scope have a break instr? */
protected boolean hasBreakInstrs;
/** Can this scope receive breaks */
protected boolean canReceiveBreaks;
/** Does this scope have a non-local return instr? */
protected boolean hasNonlocalReturns;
/** Can this scope receive a non-local return? */
public boolean canReceiveNonlocalReturns;
/** Since backref ($~) and lastline ($_) vars are allocated space on the dynamic scope,
* this is an useful flag to compute. */
private boolean usesBackrefOrLastline;
/** Does this scope call any zsuper */
private boolean usesZSuper;
/** Does this scope have loops? */
private boolean hasLoops;
/** # of thread poll instrs added to this scope */
private int threadPollInstrsCount;
/** Does this scope have explicit call protocol instructions?
* If yes, there are IR instructions for managing bindings/frames, etc.
* If not, this has to be managed implicitly as in the current runtime
* For now, only dyn-scopes are managed explicitly.
* Others will come in time */
private boolean hasExplicitCallProtocol;
/** Should we re-run compiler passes -- yes after we've inlined, for example */
private boolean relinearizeCFG;
private IRManager manager;
// Used by cloning code
protected IRScope(IRScope s, IRScope lexicalParent) {
this.lexicalParent = lexicalParent;
this.manager = s.manager;
this.fileName = s.fileName;
this.lineNumber = s.lineNumber;
this.staticScope = s.staticScope;
this.threadPollInstrsCount = s.threadPollInstrsCount;
this.nextClosureIndex = s.nextClosureIndex;
this.temporaryVariableIndex = s.temporaryVariableIndex;
this.hasLoops = s.hasLoops;
this.hasUnusedImplicitBlockArg = s.hasUnusedImplicitBlockArg;
this.instrList = null;
this.nestedClosures = new ArrayList();
this.dfProbs = new HashMap();
this.nextVarIndex = new HashMap(); // SSS FIXME: clone!
this.cfg = null;
this.linearizedInstrArray = null;
this.linearizedBBList = null;
this.flagsComputed = s.flagsComputed;
this.canModifyCode = s.canModifyCode;
this.canCaptureCallersBinding = s.canCaptureCallersBinding;
this.receivesKeywordArgs = s.receivesKeywordArgs;
this.hasBreakInstrs = s.hasBreakInstrs;
this.hasNonlocalReturns = s.hasNonlocalReturns;
this.canReceiveBreaks = s.canReceiveBreaks;
this.canReceiveNonlocalReturns = s.canReceiveNonlocalReturns;
this.bindingHasEscaped = s.bindingHasEscaped;
this.usesEval = s.usesEval;
this.usesBackrefOrLastline = s.usesBackrefOrLastline;
this.usesZSuper = s.usesZSuper;
this.hasExplicitCallProtocol = s.hasExplicitCallProtocol;
this.localVars = new LocalVariableAllocator(); // SSS FIXME: clone!
this.localVars.nextSlot = s.localVars.nextSlot;
this.relinearizeCFG = false;
setupLexicalContainment();
}
public IRScope(IRManager manager, IRScope lexicalParent, String name,
String fileName, int lineNumber, StaticScope staticScope) {
this.manager = manager;
this.lexicalParent = lexicalParent;
this.name = name;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.staticScope = staticScope;
this.threadPollInstrsCount = 0;
this.nextClosureIndex = 0;
this.temporaryVariableIndex = -1;
this.instrList = new ArrayList();
this.nestedClosures = new ArrayList();
this.dfProbs = new HashMap();
this.nextVarIndex = new HashMap();
this.cfg = null;
this.linearizedInstrArray = null;
this.linearizedBBList = null;
this.hasLoops = false;
this.hasUnusedImplicitBlockArg = false;
this.flagsComputed = false;
this.receivesKeywordArgs = false;
this.hasBreakInstrs = false;
this.hasNonlocalReturns = false;
this.canReceiveBreaks = false;
this.canReceiveNonlocalReturns = false;
// These flags are true by default!
this.canModifyCode = true;
this.canCaptureCallersBinding = true;
this.bindingHasEscaped = true;
this.usesEval = true;
this.usesBackrefOrLastline = true;
this.usesZSuper = true;
this.hasExplicitCallProtocol = false;
this.localVars = new LocalVariableAllocator();
synchronized(globalScopeCount) { this.scopeId = globalScopeCount++; }
this.relinearizeCFG = false;
setupLexicalContainment();
}
private final void setupLexicalContainment() {
if (manager.isDryRun()) {
lexicalChildren = new ArrayList();
if (lexicalParent != null) lexicalParent.addChildScope(this);
}
}
private boolean hasListener() {
return manager.getIRScopeListener() != null;
}
public int getScopeId() {
return scopeId;
}
@Override
public int hashCode() {
return scopeId;
}
@Override
public boolean equals(Object other) {
if (other == null || getClass() != other.getClass()) return false;
return scopeId == ((IRScope) other).scopeId;
}
protected void addChildScope(IRScope scope) {
lexicalChildren.add(scope);
}
public List getLexicalScopes() {
return lexicalChildren;
}
public void addClosure(IRClosure c) {
nestedClosures.add(c);
}
public Instr getLastInstr() {
return instrList.get(instrList.size() - 1);
}
public void addInstrAtBeginning(Instr i) {
if (hasListener()) {
IRScopeListener listener = manager.getIRScopeListener();
listener.addedInstr(this, i, 0);
}
instrList.add(0, i);
}
public void addInstr(Instr i) {
// SSS FIXME: If more instructions set these flags, there may be
// a better way to do this by encoding flags in its own object
// and letting every instruction update it.
if (i instanceof ThreadPollInstr) threadPollInstrsCount++;
else if (i instanceof BreakInstr) this.hasBreakInstrs = true;
else if (i instanceof NonlocalReturnInstr) this.hasNonlocalReturns = true;
else if (i instanceof DefineMetaClassInstr) this.canReceiveNonlocalReturns = true;
else if (i instanceof ReceiveKeywordArgInstr || i instanceof ReceiveKeywordRestArgInstr) this.receivesKeywordArgs = true;
if (hasListener()) {
IRScopeListener listener = manager.getIRScopeListener();
listener.addedInstr(this, i, instrList.size());
}
instrList.add(i);
}
public LocalVariable getNewFlipStateVariable() {
return getLocalVariable("%flip_" + allocateNextPrefixedName("%flip"), 0);
}
public void initFlipStateVariable(Variable v, Operand initState) {
// Add it to the beginning
instrList.add(0, new CopyInstr(v, initState));
}
public boolean isForLoopBody() {
return false;
}
public Label getNewLabel(String prefix) {
return new Label(prefix + "_" + allocateNextPrefixedName(prefix));
}
public Label getNewLabel() {
return getNewLabel("LBL");
}
public List getClosures() {
return nestedClosures;
}
public IRManager getManager() {
return manager;
}
/**
* Returns the lexical scope that contains this scope definition
*/
public IRScope getLexicalParent() {
return lexicalParent;
}
public StaticScope getStaticScope() {
return staticScope;
}
public IRMethod getNearestMethod() {
IRScope current = this;
while (current != null && !(current instanceof IRMethod)) {
current = current.getLexicalParent();
}
return (IRMethod) current;
}
public IRScope getNearestFlipVariableScope() {
IRScope current = this;
while (current != null && !current.isFlipScope()) {
current = current.getLexicalParent();
}
return current;
}
public IRScope getNearestTopLocalVariableScope() {
IRScope current = this;
while (current != null && !current.isTopLocalVariableScope()) {
current = current.getLexicalParent();
}
return current;
}
/**
* Returns the nearest scope which we can extract a live module from. If
* this returns null (like for evals), then it means it cannot be statically
* determined.
*/
public IRScope getNearestModuleReferencingScope() {
IRScope current = this;
while (!(current instanceof IRModuleBody)) {
// When eval'ing, we dont have a lexical view of what module we are nested in
// because binding_eval, class_eval, module_eval, instance_eval can switch
// around the lexical scope for evaluation to be something else.
if (current == null || current instanceof IREvalScript) return null;
current = current.getLexicalParent();
}
return current;
}
public String getName() {
return name;
}
public void setName(String name) { // This is for IRClosure ;(
this.name = name;
}
public String getFileName() {
return fileName;
}
public int getLineNumber() {
return lineNumber;
}
/**
* Returns the top level scope
*/
public IRScope getTopLevelScope() {
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 setHasLoopsFlag(boolean f) {
hasLoops = true;
}
public boolean hasLoops() {
return hasLoops;
}
public boolean hasExplicitCallProtocol() {
return hasExplicitCallProtocol;
}
public void setExplicitCallProtocolFlag(boolean flag) {
this.hasExplicitCallProtocol = flag;
}
public void setCodeModificationFlag(boolean f) {
canModifyCode = f;
}
public boolean receivesKeywordArgs() {
return this.receivesKeywordArgs;
}
public boolean modifiesCode() {
return canModifyCode;
}
public boolean bindingHasEscaped() {
return bindingHasEscaped;
}
public boolean usesBackrefOrLastline() {
return usesBackrefOrLastline;
}
public boolean usesEval() {
return usesEval;
}
public boolean usesZSuper() {
return usesZSuper;
}
public boolean canCaptureCallersBinding() {
return canCaptureCallersBinding;
}
public boolean canReceiveNonlocalReturns() {
if (this.canReceiveNonlocalReturns) {
return true;
}
boolean canReceiveNonlocalReturns = false;
for (IRClosure cl : getClosures()) {
if (cl.hasNonlocalReturns || cl.canReceiveNonlocalReturns()) {
canReceiveNonlocalReturns = true;
}
}
return canReceiveNonlocalReturns;
}
public CFG buildCFG() {
cfg = new CFG(this);
cfg.build(instrList);
// Clear out instruction list after CFG has been built.
this.instrList = null;
return cfg;
}
protected void setCFG(CFG cfg) {
this.cfg = cfg;
}
public CFG getCFG() {
return cfg;
}
private void setupLabelPCs(HashMap
© 2015 - 2025 Weber Informatics LLC | Privacy Policy