org.jruby.ir.IRScope Maven / Gradle / Ivy
package org.jruby.ir;
import org.jruby.ParseResult;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.ir.dataflow.analyses.LiveVariablesProblem;
import org.jruby.ir.dataflow.analyses.StoreLocalVarPlacementProblem;
import org.jruby.ir.dataflow.analyses.UnboxableOpsAnalysisProblem;
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.operands.Float;
import org.jruby.ir.passes.*;
import org.jruby.ir.representations.BasicBlock;
import org.jruby.ir.representations.CFG;
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.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import org.jruby.runtime.Helpers;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import static org.jruby.ir.IRFlags.*;
/**
* 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 implements ParseResult {
public static final Logger LOG = LoggerFactory.getLogger(IRScope.class);
private static final Collection NO_CLOSURES = Collections.unmodifiableCollection(new ArrayList(0));
private static AtomicInteger globalScopeCount = new AtomicInteger();
/** Unique global scope id */
private int scopeId;
/** Name */
private String name;
/** Starting line for this scope's definition */
private final int lineNumber;
/** Lexical parent scope */
private IRScope lexicalParent;
/** List of (nested) closures in this scope */
private List nestedClosures;
// 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.
private List lexicalChildren;
/** Parser static-scope that this IR scope corresponds to */
private final StaticScope staticScope;
/** Local variables defined in this scope */
private Set definedLocalVars;
/** Local variables used in this scope */
private Set usedLocalVars;
/** Startup interpretation depends on this */
protected InterpreterContext interpreterContext;
/** -X-C full interpretation OR JIT depends on this */
protected FullInterpreterContext fullInterpreterContext;
protected int temporaryVariableIndex;
protected int floatVariableIndex;
protected int fixnumVariableIndex;
protected int booleanVariableIndex;
/** Keeps track of types of prefix indexes for variables and labels */
private Map nextVarIndex;
private TemporaryLocalVariable currentModuleVariable;
private TemporaryLocalVariable currentScopeVariable;
Map localVars;
EnumSet flags = EnumSet.noneOf(IRFlags.class);
/** Have scope flags been computed? */
private boolean flagsComputed;
/** # of thread poll instrs added to this scope */
protected int threadPollInstrsCount;
private IRManager manager;
private TemporaryVariable yieldClosureVariable;
// Used by cloning code
protected IRScope(IRScope s, IRScope lexicalParent) {
this.lexicalParent = lexicalParent;
this.manager = s.manager;
this.lineNumber = s.lineNumber;
this.staticScope = s.staticScope;
this.threadPollInstrsCount = s.threadPollInstrsCount;
this.nextClosureIndex = s.nextClosureIndex;
this.temporaryVariableIndex = s.temporaryVariableIndex;
this.floatVariableIndex = s.floatVariableIndex;
this.nextVarIndex = new HashMap<>(1); // SSS FIXME: clone!
this.interpreterContext = null;
this.flagsComputed = s.flagsComputed;
this.flags = s.flags.clone();
this.localVars = new HashMap<>(s.localVars);
this.scopeId = globalScopeCount.getAndIncrement();
setupLexicalContainment();
}
public IRScope(IRManager manager, IRScope lexicalParent, String name,
int lineNumber, StaticScope staticScope) {
this.manager = manager;
this.lexicalParent = lexicalParent;
this.name = name;
this.lineNumber = lineNumber;
this.staticScope = staticScope;
this.threadPollInstrsCount = 0;
this.nextClosureIndex = 0;
this.temporaryVariableIndex = -1;
this.floatVariableIndex = -1;
this.nextVarIndex = new HashMap<>(1);
this.interpreterContext = null;
this.flagsComputed = false;
flags.remove(CAN_RECEIVE_BREAKS);
flags.remove(CAN_RECEIVE_NONLOCAL_RETURNS);
flags.remove(HAS_BREAK_INSTRS);
flags.remove(HAS_END_BLOCKS);
flags.remove(HAS_EXPLICIT_CALL_PROTOCOL);
flags.remove(HAS_LOOPS);
flags.remove(HAS_NONLOCAL_RETURNS);
flags.remove(RECEIVES_KEYWORD_ARGS);
// These flags are true by default!
flags.add(CAN_CAPTURE_CALLERS_BINDING);
flags.add(BINDING_HAS_ESCAPED);
flags.add(USES_EVAL);
flags.add(USES_BACKREF_OR_LASTLINE);
flags.add(REQUIRES_DYNSCOPE);
flags.add(USES_ZSUPER);
// We only can compute this once since 'module X; using A; class B; end; end' vs
// 'module X; class B; using A; end; end'. First case B can see refinements and in second it cannot.
if (parentMaybeUsingRefinements()) flags.add(MAYBE_USING_REFINEMENTS);
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;
}
public void setInterpreterContext(InterpreterContext interpreterContext) {
this.interpreterContext = interpreterContext;
}
@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 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 LocalVariable getNewFlipStateVariable() {
return getLocalVariable("%flip_" + allocateNextPrefixedName("%flip"), 0);
}
public Label getNewLabel(String prefix) {
return new Label(prefix, allocateNextPrefixedName(prefix));
}
public Label getNewLabel() {
return getNewLabel("LBL");
}
public Collection getClosures() {
return nestedClosures == null ? NO_CLOSURES : nestedClosures;
}
public IRManager getManager() {
return manager;
}
public void setIsMaybeUsingRefinements() {
flags.add(MAYBE_USING_REFINEMENTS);
}
public boolean parentMaybeUsingRefinements() {
for (IRScope s = this; s != null; s = s.getLexicalParent()) {
if (s.getFlags().contains(MAYBE_USING_REFINEMENTS)) return true;
// Evals cannot see outer scope 'using'
if (s instanceof IREvalScript) return false;
}
return false;
}
public boolean maybeUsingRefinements() {
return getFlags().contains(MAYBE_USING_REFINEMENTS);
}
/**
* 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 int getNearestModuleReferencingScopeDepth() {
int n = 0;
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 -1;
current = current.getLexicalParent();
if (!(current instanceof IRFor)) n++;
}
return n;
}
public String getName() {
return name;
}
public void setName(String name) { // This is for IRClosure and IRMethod ;(
this.name = name;
}
public void setFileName(String filename) {
getTopLevelScope().setFileName(filename);
}
public String getFileName() {
return getTopLevelScope().getFileName();
}
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() {
flags.add(HAS_LOOPS);
}
public boolean hasLoops() {
return flags.contains(HAS_LOOPS);
}
public boolean hasExplicitCallProtocol() {
return flags.contains(HAS_EXPLICIT_CALL_PROTOCOL);
}
public void setExplicitCallProtocolFlag() {
flags.add(HAS_EXPLICIT_CALL_PROTOCOL);
}
public boolean receivesKeywordArgs() {
return flags.contains(RECEIVES_KEYWORD_ARGS);
}
public boolean bindingHasEscaped() {
return flags.contains(BINDING_HAS_ESCAPED);
}
public boolean usesBackrefOrLastline() {
return flags.contains(USES_BACKREF_OR_LASTLINE);
}
public boolean usesEval() {
return flags.contains(USES_EVAL);
}
public boolean usesZSuper() {
return flags.contains(USES_ZSUPER);
}
public boolean canReceiveNonlocalReturns() {
computeScopeFlags();
return flags.contains(CAN_RECEIVE_NONLOCAL_RETURNS);
}
public void putLiveVariablesProblem(LiveVariablesProblem problem) {
// Technically this is if a pass is invalidated which has never run on a scope with no CFG/FIC yet.
if (fullInterpreterContext == null) {
// This should never trigger unless we got sloppy
if (problem != null) throw new IllegalStateException("LVP being stored when no FIC");
return;
}
fullInterpreterContext.getDataFlowProblems().put(LiveVariablesProblem.NAME, problem);
}
public LiveVariablesProblem getLiveVariablesProblem() {
if (fullInterpreterContext == null) return null; // no fic so no pass-related info
return (LiveVariablesProblem) fullInterpreterContext.getDataFlowProblems().get(LiveVariablesProblem.NAME);
}
public void putStoreLocalVarPlacementProblem(StoreLocalVarPlacementProblem problem) {
// Technically this is if a pass is invalidated which has never run on a scope with no CFG/FIC yet.
if (fullInterpreterContext == null) {
// This should never trigger unless we got sloppy
if (problem != null) throw new IllegalStateException("StoreLocalVarPlacementProblem being stored when no FIC");
return;
}
fullInterpreterContext.getDataFlowProblems().put(StoreLocalVarPlacementProblem.NAME, problem);
}
public StoreLocalVarPlacementProblem getStoreLocalVarPlacementProblem() {
if (fullInterpreterContext == null) return null; // no fic so no pass-related info
return (StoreLocalVarPlacementProblem) fullInterpreterContext.getDataFlowProblems().get(StoreLocalVarPlacementProblem.NAME);
}
public void putUnboxableOpsAnalysisProblem(UnboxableOpsAnalysisProblem problem) {
// Technically this is if a pass is invalidated which has never run on a scope with no CFG/FIC yet.
if (fullInterpreterContext == null) {
// This should never trigger unless we got sloppy
if (problem != null) throw new IllegalStateException("UboxableOpsAnalysisProblem being stored when no FIC");
return;
}
fullInterpreterContext.getDataFlowProblems().put(UnboxableOpsAnalysisProblem.NAME, problem);
}
public UnboxableOpsAnalysisProblem getUnboxableOpsAnalysisProblem() {
if (fullInterpreterContext == null) return null; // no fic so no pass-related info
return (UnboxableOpsAnalysisProblem) fullInterpreterContext.getDataFlowProblems().get(UnboxableOpsAnalysisProblem.NAME);
}
public CFG getCFG() {
// A child scope may not have been prepared yet so we advance it to point of have a fresh CFG.
if (getFullInterpreterContext() == null) prepareFullBuildCommon();
return fullInterpreterContext.getCFG();
}
protected boolean isUnsafeScope() {
if (this.isBeginEndBlock()) return true; // this is a BEGIN block
List beginBlocks = getBeginBlocks();
if (beginBlocks != null && !beginBlocks.isEmpty()) return true; // this contains a BEGIN block
// Does topmost variable scope contain any BEGIN blocks (IRScriptBody or IREval)?
// Ex1: eval("BEGIN {a = 1}; p a") Ex2: BEGIN {a = 1}; p a
beginBlocks = getNearestTopLocalVariableScope().getBeginBlocks();
return beginBlocks != null && !beginBlocks.isEmpty();
}
public List getExecutedPasses() {
return fullInterpreterContext == null ? new ArrayList(1) : fullInterpreterContext.getExecutedPasses();
}
// SSS FIXME: We should configure different optimization levels
// and run different kinds of analysis depending on time budget.
// Accordingly, we need to set IR levels/states (basic, optimized, etc.)
private void runCompilerPasses(List passes, IGVDumper dumper) {
// All passes are disabled in scopes where BEGIN and END scopes might
// screw around with escaped variables. Optimizing for them is not
// worth the effort. It is simpler to just go fully safe in scopes
// influenced by their presence.
if (isUnsafeScope()) {
passes = getManager().getSafePasses(this);
}
if (dumper != null) dumper.dump(getCFG(), "Start");
CompilerPassScheduler scheduler = IRManager.schedulePasses(passes);
for (CompilerPass pass : scheduler) {
pass.run(this);
if (dumper != null) dumper.dump(getCFG(), pass.getShortLabel());
}
if (RubyInstanceConfig.IR_UNBOXING) {
CompilerPass pass = new UnboxingPass();
pass.run(this);
if (dumper != null) dumper.dump(getCFG(), pass.getShortLabel());
}
if (dumper != null) dumper.close();
}
/** Make version specific to scope which needs it (e.g. Closure vs non-closure). */
public InterpreterContext allocateInterpreterContext(List instructions) {
interpreterContext = new InterpreterContext(this, instructions);
if (RubyInstanceConfig.IR_COMPILER_DEBUG) LOG.info(interpreterContext.toString());
return interpreterContext;
}
/** Make version specific to scope which needs it (e.g. Closure vs non-closure). */
public InterpreterContext allocateInterpreterContext(Callable> instructions) {
try {
interpreterContext = new InterpreterContext(this, instructions);
} catch (Exception e) {
Helpers.throwException(e);
}
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;
}
private void prepareFullBuildCommon() {
// We already made it.
if (fullInterpreterContext != null) return;
// Clone instrs from startup interpreter so we do not swap out instrs out from under the
// startup interpreter as we are building the full interpreter.
fullInterpreterContext = new FullInterpreterContext(this, cloneInstrs());
}
/**
* This initializes a more complete(full) InterpreterContext which if used in mixed mode will be
* used by the JIT and if used in pure-interpreted mode it will be used by an interpreter engine.
*/
public synchronized FullInterpreterContext prepareFullBuild() {
// Don't run if same method was queued up in the tiny race for scheduling JIT/Full Build OR
// for any nested closures which got a a fullInterpreterContext but have not run any passes
// or generated instructions.
if (fullInterpreterContext != null && fullInterpreterContext.buildComplete()) return fullInterpreterContext;
for (IRScope scope: getClosures()) {
scope.prepareFullBuild();
}
prepareFullBuildCommon();
runCompilerPasses(getManager().getCompilerPasses(this), dumpToIGV());
getManager().optimizeIfSimpleScope(this);
// Always add call protocol instructions now since we are removing support for implicit stuff in interp.
if (!isUnsafeScope()) new AddCallProtocolInstructions().run(this);
fullInterpreterContext.generateInstructionsForInterpretation();
return fullInterpreterContext;
}
public String getFullyQualifiedName() {
if (getLexicalParent() == null) return getName();
return getLexicalParent().getFullyQualifiedName() + "::" + getName();
}
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;
}
/** Run any necessary passes to get the IR ready for compilation (AOT and/or JIT) */
public synchronized BasicBlock[] prepareForCompilation() {
// Don't run if same method was queued up in the tiny race for scheduling JIT/Full Build OR
// for any nested closures which got a a fullInterpreterContext but have not run any passes
// or generated instructions.
if (fullInterpreterContext != null && fullInterpreterContext.buildComplete()) return fullInterpreterContext.getLinearizedBBList();
for (IRScope scope: getClosures()) {
scope.prepareForCompilation();
}
prepareFullBuildCommon();
runCompilerPasses(getManager().getJITPasses(this), dumpToIGV());
BasicBlock[] bbs = fullInterpreterContext.linearizeBasicBlocks();
return bbs;
}
// FIXME: For inlining, culmulative or extra passes run based on profiled execution we need to re-init data or even
// construct a new fullInterpreterContext. Primary obstacles is JITFlags and linearization of BBs.
public Map buildJVMExceptionTable() {
Map map = new HashMap<>(1);
for (BasicBlock bb: fullInterpreterContext.getLinearizedBBList()) {
BasicBlock rescueBB = getCFG().getRescuerBBFor(bb);
if (rescueBB != null) {
map.put(bb, rescueBB.getLabel());
}
}
// SSS FIXME: This could be optimized by compressing entries for adjacent BBs that have identical handlers
// This could be optimized either during generation or as another pass over the table. But, if the JVM
// does that already, do we need to bother with it?
return map;
}
public EnumSet getFlags() {
return flags;
}
private void initScopeFlags() {
flags.remove(CAN_CAPTURE_CALLERS_BINDING);
flags.remove(CAN_RECEIVE_BREAKS);
flags.remove(CAN_RECEIVE_NONLOCAL_RETURNS);
flags.remove(HAS_BREAK_INSTRS);
flags.remove(HAS_NONLOCAL_RETURNS);
flags.remove(USES_ZSUPER);
flags.remove(USES_EVAL);
flags.remove(USES_BACKREF_OR_LASTLINE);
flags.remove(REQUIRES_DYNSCOPE);
}
private void bindingEscapedScopeFlagsCheck() {
// NOTE: bindingHasEscaped is the crucial flag and it effectively is
// unconditionally true whenever it has a call that receives a closure.
// See CallBase.computeRequiresCallersBindingFlag
if (this instanceof IREvalScript || this instanceof IRScriptBody) {
// For eval scopes, bindings are considered escaped.
// For top-level script scopes, bindings are considered escaped as well
// because TOPLEVEL_BINDING can be used in places besides the file
// that is being parsed?
flags.add(BINDING_HAS_ESCAPED);
} else {
flags.remove(BINDING_HAS_ESCAPED);
}
}
private void calculateClosureScopeFlags() {
// Compute flags for nested closures (recursively) and set derived flags.
for (IRClosure cl: getClosures()) {
cl.computeScopeFlags();
if (cl.usesEval()) {
flags.add(CAN_RECEIVE_BREAKS);
flags.add(CAN_RECEIVE_NONLOCAL_RETURNS);
flags.add(USES_ZSUPER);
} else {
if (cl.flags.contains(HAS_BREAK_INSTRS) || cl.flags.contains(CAN_RECEIVE_BREAKS)) {
flags.add(CAN_RECEIVE_BREAKS);
}
if (cl.flags.contains(HAS_NONLOCAL_RETURNS) || cl.flags.contains(CAN_RECEIVE_NONLOCAL_RETURNS)) {
flags.add(CAN_RECEIVE_NONLOCAL_RETURNS);
}
if (cl.usesZSuper()) {
flags.add(USES_ZSUPER);
}
}
}
}
private static final EnumSet NEEDS_DYNAMIC_SCOPE_FLAGS =
EnumSet.of(
CAN_RECEIVE_BREAKS,
HAS_NONLOCAL_RETURNS,CAN_RECEIVE_NONLOCAL_RETURNS,
BINDING_HAS_ESCAPED);
private void computeNeedsDynamicScopeFlag() {
for (IRFlags f : NEEDS_DYNAMIC_SCOPE_FLAGS) {
if (flags.contains(f)) {
flags.add(REQUIRES_DYNSCOPE);
return;
}
}
}
// ENEBO: IRBuild adds more instrs after this so should we force a recompute?
/**
* This is called when building an IRMethod before it has completed the build and made an IC
* yet.
*/
public void computeScopeFlagsEarly(List instructions) {
initScopeFlags();
bindingEscapedScopeFlagsCheck();
for (Instr i : instructions) {
i.computeScopeFlags(this);
}
calculateClosureScopeFlags();
computeNeedsDynamicScopeFlag();
flagsComputed = true;
}
/**
* Calculate scope flags used by various passes to know things like whether a binding has escaped.
* We may recalculate flags in a few scenarios:
* - once after IR generation and local optimizations propagates constants locally
* - also potentially at later times after other opt passes
*/
public void computeScopeFlags() {
if (flagsComputed) return;
initScopeFlags();
bindingEscapedScopeFlagsCheck();
if (fullInterpreterContext != null) {
fullInterpreterContext.computeScopeFlagsFromInstructions();
} else {
interpreterContext.computeScopeFlagsFromInstructions();
}
calculateClosureScopeFlags();
computeNeedsDynamicScopeFlag();
flagsComputed = true;
}
public abstract IRScopeType getScopeType();
@Override
public String toString() {
return String.valueOf(getScopeType()) + ' ' + getName() + '[' + getFileName() + ':' + getLineNumber() + ']';
}
public String debugOutput() {
return toStringInstrs();
}
public String toStringInstrs() {
if (fullInterpreterContext != null) { // JIT or Full interpreter
return "Instructions:\n" + fullInterpreterContext.toStringInstrs();
} else { // Startup interpreter
return interpreterContext.toStringInstrs();
}
}
public Variable getSelf() {
return Self.SELF;
}
public Variable getCurrentModuleVariable() {
// SSS: Used in only 3 cases in generated IR:
// -> searching a constant in the inheritance hierarchy
// -> searching a super-method in the inheritance hierarchy
// -> looking up 'StandardError' (which can be eliminated by creating a special operand type for this)
if (currentModuleVariable == null) {
temporaryVariableIndex++;
currentModuleVariable = TemporaryCurrentModuleVariable.ModuleVariableFor(temporaryVariableIndex);
}
return currentModuleVariable;
}
public Variable getCurrentScopeVariable() {
// SSS: Used in only 1 case in generated IR:
// -> searching a constant in the lexical scope hierarchy
if (currentScopeVariable == null) {
temporaryVariableIndex++;
currentScopeVariable = TemporaryCurrentScopeVariable.ScopeVariableFor(temporaryVariableIndex);
}
return currentScopeVariable;
}
/**
* Get the local variables for this scope.
* This should only be used by persistence layer.
*/
public Map getLocalVariables() {
return localVars;
}
/**
* Get all variables referenced by this scope.
*/
public Set getUsedLocalVariables() {
return usedLocalVars;
}
/**
* Set the local variables for this scope. This should only be used by persistence layer.
*/
// FIXME: Consider making constructor for persistence to pass in all of this stuff
public void setLocalVariables(Map variables) {
this.localVars = variables;
}
public void setLabelIndices(Map indices) {
nextVarIndex = indices;
}
public LocalVariable lookupExistingLVar(String name) {
return localVars.get(name);
}
protected LocalVariable findExistingLocalVariable(String name, int depth) {
return localVars.get(name);
}
/**
* Find or create a local variable. By default, scopes are assumed to
* only check current depth. Blocks/Closures override this because they
* have special nesting rules.
*/
public LocalVariable getLocalVariable(String 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(String 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));
localVars.put(name, lvar);
return lvar;
}
public TemporaryLocalVariable createTemporaryVariable() {
return getNewTemporaryVariable(TemporaryVariableType.LOCAL);
}
public TemporaryLocalVariable getNewTemporaryVariableFor(LocalVariable var) {
temporaryVariableIndex++;
return new TemporaryLocalReplacementVariable(var.getName(), temporaryVariableIndex);
}
public TemporaryLocalVariable getNewTemporaryVariable(TemporaryVariableType type) {
switch (type) {
case FLOAT: {
floatVariableIndex++;
return new TemporaryFloatVariable(floatVariableIndex);
}
case FIXNUM: {
fixnumVariableIndex++;
return new TemporaryFixnumVariable(fixnumVariableIndex);
}
case BOOLEAN: {
booleanVariableIndex++;
return new TemporaryBooleanVariable(booleanVariableIndex);
}
case LOCAL: {
temporaryVariableIndex++;
return manager.newTemporaryLocalVariable(temporaryVariableIndex);
}
}
throw new RuntimeException("Invalid temporary variable being alloced in this scope: " + type);
}
public void setTemporaryVariableCount(int count) {
temporaryVariableIndex = count + 1;
}
/**
* Get the variable for accessing the "yieldable" closure in this scope.
*/
public TemporaryVariable getYieldClosureVariable() {
if (yieldClosureVariable == null) {
return yieldClosureVariable = createTemporaryVariable();
}
return yieldClosureVariable;
}
public TemporaryLocalVariable getNewUnboxedVariable(Class type) {
TemporaryVariableType varType;
if (type == Float.class) {
varType = TemporaryVariableType.FLOAT;
} else if (type == Fixnum.class) {
varType = TemporaryVariableType.FIXNUM;
} else if (type == java.lang.Boolean.class) {
varType = TemporaryVariableType.BOOLEAN;
} else {
varType = TemporaryVariableType.LOCAL;
}
return getNewTemporaryVariable(varType);
}
public void resetTemporaryVariables() {
temporaryVariableIndex = -1;
floatVariableIndex = -1;
fixnumVariableIndex = -1;
booleanVariableIndex = -1;
}
public int getTemporaryVariablesCount() {
return temporaryVariableIndex + 1;
}
public int getFloatVariablesCount() {
return floatVariableIndex + 1;
}
public int getFixnumVariablesCount() {
return fixnumVariableIndex + 1;
}
public int getBooleanVariablesCount() {
return booleanVariableIndex + 1;
}
// Generate a new variable for inlined code
public Variable getNewInlineVariable(String inlinePrefix, Variable v) {
if (v instanceof LocalVariable) {
LocalVariable lv = (LocalVariable)v;
return getLocalVariable(inlinePrefix + lv.getName(), lv.getScopeDepth());
} else {
return createTemporaryVariable();
}
}
public int getThreadPollInstrsCount() {
return threadPollInstrsCount;
}
public int getLocalVariablesCount() {
return localVars.size();
}
public int getUsedVariablesCount() {
// System.out.println("For " + this + ", # lvs: " + getLocalVariablesCount());
// # local vars, # flip vars
//
// SSS FIXME: When we are opting local var access,
// no need to allocate local var space except when we have been asked to!
return getLocalVariablesCount() + getPrefixCountSize("%flip");
}
public void setUpUseDefLocalVarMaps() {
definedLocalVars = new HashSet<>(1);
usedLocalVars = new HashSet<>(1);
for (BasicBlock bb : getCFG().getBasicBlocks()) {
for (Instr i : bb.getInstrs()) {
for (Variable v : i.getUsedVariables()) {
if (v instanceof LocalVariable) usedLocalVars.add((LocalVariable) v);
}
if (i instanceof ResultInstr) {
Variable v = ((ResultInstr) i).getResult();
if (v instanceof LocalVariable && ((LocalVariable)v).getScopeDepth() == 0) {
definedLocalVars.add((LocalVariable) v);
}
}
}
}
for (IRClosure cl : getClosures()) {
cl.setUpUseDefLocalVarMaps();
}
}
public boolean usesLocalVariable(Variable v) {
if (usedLocalVars == null) setUpUseDefLocalVarMaps();
if (usedLocalVars.contains(v)) return true;
for (IRClosure cl : getClosures()) {
if (cl.usesLocalVariable(v)) return true;
}
return false;
}
public boolean definesLocalVariable(Variable v) {
if (definedLocalVars == null) setUpUseDefLocalVarMaps();
if (definedLocalVars.contains(v)) return true;
for (IRClosure cl : getClosures()) {
if (cl.definesLocalVariable(v)) return true;
}
return false;
}
/**
* For lazy scopes which IRBuild on demand we can ask this method whether it has been built yet...
*/
public boolean hasBeenBuilt() {
return true;
}
public FullInterpreterContext getExecutionContext() {
return fullInterpreterContext;
}
public InterpreterContext getInterpreterContext() {
return interpreterContext;
}
public FullInterpreterContext getFullInterpreterContext() {
return fullInterpreterContext;
}
protected void depends(Object obj) {
assert obj != null: "Unsatisfied dependency and this depends() was set " +
"up wrong. Use depends(build()) not depends(build).";
}
public void resetState() {
interpreterContext = null;
fullInterpreterContext = null;
// reset flags
flagsComputed = false;
flags.add(CAN_CAPTURE_CALLERS_BINDING);
flags.add(BINDING_HAS_ESCAPED);
flags.add(USES_EVAL);
flags.add(USES_ZSUPER);
flags.remove(HAS_BREAK_INSTRS);
flags.remove(HAS_NONLOCAL_RETURNS);
flags.remove(CAN_RECEIVE_BREAKS);
flags.remove(CAN_RECEIVE_NONLOCAL_RETURNS);
// Invalidate compiler pass state.
//
// SSS FIXME: Re-grabbing passes each iter is to get around concurrent-modification issues
// since CompilerPass.invalidate modifies this, but some passes cannot be invalidated. This
// should be wrapped in an iterator.
FullInterpreterContext fic = getFullInterpreterContext();
if (fic != null) {
int i = 0;
while (i < fic.getExecutedPasses().size()) {
if (!fic.getExecutedPasses().get(i).invalidate(this)) {
i++;
}
}
}
}
public void inlineMethod(IRScope method, RubyModule implClass, int classToken, BasicBlock basicBlock, CallBase call, boolean cloneHost) {
// Inline
new CFGInliner(getCFG()).inlineMethod(method, implClass, classToken, basicBlock, call, cloneHost);
// Reset state
resetState();
// Re-run opts
for (CompilerPass pass: getManager().getInliningCompilerPasses(this)) {
pass.run(this);
}
}
/** Record a begin block. Only eval and script body scopes support this */
public void recordBeginBlock(IRClosure beginBlockClosure) {
throw new RuntimeException("BEGIN blocks cannot be added to: " + this.getClass().getName());
}
public List getBeginBlocks() {
return null;
}
public List getEndBlocks() {
return null;
}
// Enebo: We should just make n primitive int and not take the hash hit
protected int allocateNextPrefixedName(String prefix) {
int index = getPrefixCountSize(prefix);
nextVarIndex.put(prefix, index + 1);
return index;
}
protected void resetVariableCounter(String prefix) {
nextVarIndex.remove(prefix);
}
public Map getVarIndices() {
return nextVarIndex;
}
protected int getPrefixCountSize(String prefix) {
Integer index = nextVarIndex.get(prefix);
if (index == null) return 0;
return index.intValue();
}
public int getNextClosureId() {
nextClosureIndex++;
return nextClosureIndex;
}
public boolean isBeginEndBlock() {
return false;
}
/**
* Does this scope represent a module body?
*/
public boolean isModuleBody() {
return false;
}
/**
* Is this IRClassBody but not IRMetaClassBody?
*/
public boolean isNonSingletonClassBody() {
return false;
}
public boolean isFlipScope() {
return true;
}
public boolean isTopLocalVariableScope() {
return true;
}
/**
* Is this an eval script or a regular file script?
*/
public boolean isScriptScope() {
return false;
}
public boolean needsFrame() {
boolean bindingHasEscaped = bindingHasEscaped();
boolean requireFrame = bindingHasEscaped || usesEval();
for (IRFlags flag : getFlags()) {
switch (flag) {
case BINDING_HAS_ESCAPED:
case CAN_CAPTURE_CALLERS_BINDING:
case REQUIRES_FRAME:
case REQUIRES_VISIBILITY:
case USES_BACKREF_OR_LASTLINE:
case USES_EVAL:
case USES_ZSUPER:
requireFrame = true;
}
}
return requireFrame;
}
public boolean reuseParentScope() {
return getFlags().contains(IRFlags.REUSE_PARENT_DYNSCOPE);
}
public boolean needsBinding() {
return reuseParentScope() || !getFlags().contains(IRFlags.DYNSCOPE_ELIMINATED);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy