@Override public void applyTransferFunction(Instr i) { IRScope scope = problem.getScope(); boolean scopeBindingHasEscaped = scope.bindingHasEscaped(); // Right away, clear the variable defined by this instruction -- it doesn't have to be loaded! if (i instanceof ResultInstr) { reqdLoads.remove(((ResultInstr) i).getResult()); } // Process closure accepting instrs specially -- these are the sites of binding loads! if (i instanceof ClosureAcceptingInstr) { Operand o = ((ClosureAcceptingInstr) i).getClosureArg(); if (o != null && o instanceof WrappedIRClosure) { IRClosure cl = ((WrappedIRClosure) o).getClosure(); // Variables defined in the closure do not need to be loaded anymore at // program points before the call, because they will be loaded after the // call completes to fetch the latest value. // // Allocate a new hash-set and modify it to get around ConcurrentModificationException on // reqdLoads Set<LocalVariable> newReqdLoads = new HashSet<LocalVariable>(reqdLoads); for (LocalVariable v : reqdLoads) { if (cl.definesLocalVariable(v)) newReqdLoads.remove(v); } reqdLoads = newReqdLoads; } // In this case, we are going to blindly load everything -- so, at the call site, pending // loads dont carry over! if (scopeBindingHasEscaped) { reqdLoads.clear(); } else { // All variables not defined in the current scope have to be always loaded // because of multi-threading scenarios where some other scope // could update this variable concurrently. // // Allocate a new hash-set and modify it to get around ConcurrentModificationException on // reqdLoads Set<LocalVariable> newReqdLoads = new HashSet<LocalVariable>(reqdLoads); for (LocalVariable v : reqdLoads) { if (!scope.definesLocalVariable(v)) newReqdLoads.remove(v); } reqdLoads = newReqdLoads; } } else if (scopeBindingHasEscaped && (i.getOperation() == Operation.PUT_GLOBAL_VAR)) { // global-var tracing can execute closures set up in previous trace-var calls // in which case we would have the 'scopeBindingHasEscaped' flag set to true reqdLoads.clear(); } if (i.getOperation() == Operation.BINDING_STORE) { LocalVariable lv = ((StoreLocalVarInstr) i).getLocalVar(); if (!lv.isSelf()) reqdLoads.add(lv); } else { // The variables used as arguments will need to be loaded // %self is local to every scope and never crosses scope boundaries and need not be // spilled/refilled for (Variable x : i.getUsedVariables()) { if (x instanceof LocalVariable && !x.isSelf()) { reqdLoads.add((LocalVariable) x); } } } }
public void addLoads(Map<Operand, Operand> varRenameMap) { IRScope scope = problem.getScope(); boolean isEvalScript = scope instanceof IREvalScript; boolean scopeBindingHasEscaped = scope.bindingHasEscaped(); List<Instr> instrs = basicBlock.getInstrs(); ListIterator<Instr> it = instrs.listIterator(instrs.size()); initSolution(); while (it.hasPrevious()) { Instr i = it.previous(); // Right away, clear the variable defined by this instruction -- it doesn't have to be loaded! if (i instanceof ResultInstr) reqdLoads.remove(((ResultInstr) i).getResult()); // Process closure accepting instrs specially -- these are the sites of binding loads! if (i instanceof ClosureAcceptingInstr) { Operand o = ((ClosureAcceptingInstr) i).getClosureArg(); if (o != null && o instanceof WrappedIRClosure) { IRClosure cl = ((WrappedIRClosure) o).getClosure(); // Only those variables that are defined in the closure, and are in the required loads set // will need to be loaded from the binding after the call! Rest can wait .. // // Allocate a new hash-set and modify it to get around ConcurrentModificationException on // reqdLoads Set<LocalVariable> newReqdLoads = new HashSet<LocalVariable>(reqdLoads); it.next(); for (LocalVariable v : reqdLoads) { if (cl.definesLocalVariable(v)) { it.add( new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v)); it.previous(); newReqdLoads.remove(v); } } it.previous(); reqdLoads = newReqdLoads; } // In this case, we are going to blindly load everything if (scopeBindingHasEscaped) { it.next(); for (LocalVariable v : reqdLoads) { it.add(new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v)); it.previous(); } it.previous(); reqdLoads.clear(); } else { // All variables not defined in the current scope have to be always loaded // because of multi-threading scenarios where some other scope // could update this variable concurrently. // // Allocate a new hash-set and modify it to get around ConcurrentModificationException on // reqdLoads Set<LocalVariable> newReqdLoads = new HashSet<LocalVariable>(reqdLoads); it.next(); for (LocalVariable v : reqdLoads) { if (!scope.definesLocalVariable(v)) { it.add( new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v)); it.previous(); newReqdLoads.remove(v); } } it.previous(); reqdLoads = newReqdLoads; } } else if (scopeBindingHasEscaped && (i.getOperation() == Operation.PUT_GLOBAL_VAR)) { // global-var tracing can execute closures set up in previous trace-var calls // in which case we would have the 'scopeBindingHasEscaped' flag set to true it.next(); for (LocalVariable v : reqdLoads) { it.add(new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v)); it.previous(); } it.previous(); reqdLoads.clear(); } if (i.getOperation() == Operation.BINDING_STORE) { LocalVariable lv = ((StoreLocalVarInstr) i).getLocalVar(); if (!lv.isSelf()) { reqdLoads.add(lv); // SSS FIXME: Why is this reqd again? Document with example // Make sure there is a replacement var for all local vars getLocalVarReplacement(lv, scope, varRenameMap); } } else { // The variables used as arguments will need to be loaded // %self is local to every scope and never crosses scope boundaries and need not be // spilled/refilled for (Variable v : i.getUsedVariables()) { if (!(v instanceof LocalVariable)) continue; LocalVariable lv = (LocalVariable) v; if (!lv.isSelf()) { reqdLoads.add(lv); // SSS FIXME: Why is this reqd again? Document with example // Make sure there is a replacement var for all local vars getLocalVarReplacement(lv, scope, varRenameMap); } } } } // Add loads on entry of a rescue block. if (basicBlock.isRescueEntry()) { for (LocalVariable v : reqdLoads) { it.add(new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v)); } } // Load first use of variables in closures if (scope instanceof IRClosure && basicBlock.isEntryBB()) { // System.out.println("\n[In Entry BB] For CFG " + getCFG() + ":"); // System.out.println("\t--> Reqd loads : " + // java.util.Arrays.toString(reqdLoads.toArray())); for (LocalVariable v : reqdLoads) { if (scope.usesLocalVariable(v) || scope.definesLocalVariable(v)) { if (isEvalScript || !(v instanceof ClosureLocalVariable) || (scope != ((ClosureLocalVariable) v).definingScope)) { it.add(new LoadLocalVarInstr(scope, getLocalVarReplacement(v, scope, varRenameMap), v)); } } } } }
@Override public void applyTransferFunction(Instr i) { IRScope scope = problem.getScope(); boolean scopeBindingHasEscaped = scope.bindingHasEscaped(); // Process closure accepting instrs specially -- these are the sites of binding stores! if (i instanceof ClosureAcceptingInstr) { Operand o = ((ClosureAcceptingInstr) i).getClosureArg(); // At this site, a binding will get allocated if it has not been already! if (o != null && o instanceof WrappedIRClosure) { // In this first pass, the current scope and the call's closure are considered // independent of each other which means any variable that is used by the variable // will get spilled into the binding. This is clearly conservative, but simplifies // the analysis. IRClosure cl = ((WrappedIRClosure) o).getClosure(); // If the call is a dataflow barrier, we have to spill everything here boolean spillAllVars = scopeBindingHasEscaped; // - If all variables have to be spilled, then those variables will no longer be dirty after // the call site // - If a variable is used in the closure (FIXME: Strictly only those vars that are live at // the call site -- // but we dont have this info!), it has to be spilt. So, these variables are no longer // dirty after the call site. // - If a variable is (re)defined in the closure, it will always be loaded after the call. // So, we have to always // spill it before the call in the scenario that the closure never gets executed! So, it // won't be dirty after // the call site. Set<LocalVariable> newDirtyVars = new HashSet<LocalVariable>(dirtyVars); for (LocalVariable v : dirtyVars) { if (spillAllVars || cl.usesLocalVariable(v) || cl.definesLocalVariable(v)) { newDirtyVars.remove(v); } } dirtyVars = newDirtyVars; } else if (scopeBindingHasEscaped) { // Call has no closure && it requires stores dirtyVars.clear(); } else { // All variables not local to the current scope have to be always spilled because of // multi-threading scenarios where some other scope could load this variable concurrently. // // Allocate a new hash-set and modify it to get around ConcurrentModificationException on // dirtyVars Set<LocalVariable> newDirtyVars = new HashSet<LocalVariable>(dirtyVars); for (LocalVariable v : dirtyVars) { if ((v instanceof ClosureLocalVariable) && ((ClosureLocalVariable) v).definingScope != scope) { newDirtyVars.remove(v); } } dirtyVars = newDirtyVars; } } if (scopeBindingHasEscaped && (i.getOperation() == Operation.PUT_GLOBAL_VAR)) { // global-var tracing can execute closures set up in previous trace-var calls // in which case we would have the 'scopeBindingHasEscaped' flag set to true dirtyVars.clear(); } // If this instruction can raise an exception and we are going to be rescued, // spill all dirty vars before the instruction! if (i.canRaiseException() && hasExceptionsRescued()) { dirtyVars.clear(); } if (i instanceof ResultInstr) { Variable v = ((ResultInstr) i).getResult(); // %self is local to every scope and never crosses scope boundaries and need not be // spilled/refilled if (v instanceof LocalVariable && !v.isSelf()) dirtyVars.add((LocalVariable) v); } if (i.getOperation().isReturn()) dirtyVars.clear(); }
@Override public Object execute(IRScope scope, Object... data) { // IRScriptBody do not get explicit call protocol instructions right now. // They dont push/pop a frame and do other special things like run begin/end blocks. // So, for now, they go through the runtime stub in IRScriptBody. // // Add explicit frame and binding push/pop instrs ONLY for methods -- we cannot handle this in // closures and evals yet // If the scope uses $_ or $~ family of vars, has local load/stores, or if its binding has // escaped, we have // to allocate a dynamic scope for it and add binding push/pop instructions. if (explicitCallProtocolSupported(scope)) { StoreLocalVarPlacementProblem slvpp = (StoreLocalVarPlacementProblem) scope.getDataFlowSolution(StoreLocalVarPlacementProblem.NAME); boolean scopeHasLocalVarStores = false; boolean bindingHasEscaped = scope.bindingHasEscaped(); CFG cfg = scope.cfg(); if (slvpp != null && bindingHasEscaped) { scopeHasLocalVarStores = slvpp.scopeHasLocalVarStores(); } else { // We dont require local-var load/stores to have been run. // If it is not run, we go conservative and add push/pop binding instrs. everywhere scopeHasLocalVarStores = bindingHasEscaped; } boolean requireFrame = doesItRequireFrame(scope, bindingHasEscaped); boolean requireBinding = !scope.getFlags().contains(IRFlags.DYNSCOPE_ELIMINATED); if (requireBinding || requireFrame) { BasicBlock entryBB = cfg.getEntryBB(); // Push if (requireFrame) entryBB.addInstr(new PushFrameInstr(scope.getName())); if (requireBinding) entryBB.addInstr(new PushBindingInstr()); // SSS FIXME: We are doing this conservatively. // Only scopes that have unrescued exceptions need a GEB. // // Allocate GEB if necessary for popping BasicBlock geb = cfg.getGlobalEnsureBB(); if (geb == null) { Variable exc = scope.createTemporaryVariable(); geb = new BasicBlock(cfg, Label.getGlobalEnsureBlockLabel()); geb.addInstr( new ReceiveJRubyExceptionInstr(exc)); // JRuby Implementation exception handling geb.addInstr(new ThrowExceptionInstr(exc)); cfg.addGlobalEnsureBB(geb); } // Pop on all scope-exit paths for (BasicBlock bb : cfg.getBasicBlocks()) { Instr i = null; ListIterator<Instr> instrs = bb.getInstrs().listIterator(); while (instrs.hasNext()) { i = instrs.next(); // Right now, we only support explicit call protocol on methods. // So, non-local returns and breaks don't get here. // Non-local-returns and breaks are tricky since they almost always // throw an exception and we don't multiple pops (once before the // return/break, and once when the exception is caught). if (!bb.isExitBB() && i instanceof ReturnBase) { // Add before the break/return instrs.previous(); if (requireBinding) instrs.add(new PopBindingInstr()); if (requireFrame) instrs.add(new PopFrameInstr()); break; } } if (bb.isExitBB() && !bb.isEmpty()) { // Last instr could be a return -- so, move iterator one position back if (i != null && i instanceof ReturnBase) instrs.previous(); if (requireBinding) instrs.add(new PopBindingInstr()); if (requireFrame) instrs.add(new PopFrameInstr()); } if (bb == geb) { // Add before throw-exception-instr which would be the last instr if (i != null) { // Assumption: Last instr should always be a control-transfer instruction assert i.getOperation().transfersControl() : "Last instruction of GEB in scope: " + scope + " is " + i + ", not a control-xfer instruction"; instrs.previous(); } if (requireBinding) instrs.add(new PopBindingInstr()); if (requireFrame) instrs.add(new PopFrameInstr()); } } } // This scope has an explicit call protocol flag now scope.setExplicitCallProtocolFlag(); } // FIXME: Useless for now // Run on all nested closures. for (IRClosure c : scope.getClosures()) run(c, false, true); // LVA information is no longer valid after the pass // FIXME: Grrr ... this seems broken to have to create a new object to invalidate (new LiveVariableAnalysis()).invalidate(scope); return null; }
public boolean addStores( Map<Operand, Operand> varRenameMap, Set<LocalVariable> excTargetDirtyVars) { IRScope scope = problem.getScope(); boolean addedStores = false; boolean isClosure = scope instanceof IRClosure; boolean scopeBindingHasEscaped = scope.bindingHasEscaped(); ListIterator<Instr> instrs = basicBlock.getInstrs().listIterator(); initSolution(); // If this is the exit BB, we need a binding store on exit only for vars that are both: // // (a) dirty, // (b) live on exit from the closure // condition reqd. because the variable could be dirty but not used outside. // Ex: s=0; a.each { |i| j = i+1; sum += j; }; puts sum // i,j are dirty inside the block, but not used outside if (basicBlock.isExitBB()) { LiveVariablesProblem lvp = (LiveVariablesProblem) scope.getDataFlowSolution(DataFlowConstants.LVP_NAME); java.util.Collection<LocalVariable> liveVars = lvp.getVarsLiveOnScopeExit(); if (liveVars != null) { dirtyVars.retainAll(liveVars); // Intersection with variables live on exit from the scope } else { dirtyVars.clear(); } } while (instrs.hasNext()) { Instr i = instrs.next(); // Process closure accepting instrs specially -- these are the sites of binding stores! if (i instanceof ClosureAcceptingInstr) { Operand o = ((ClosureAcceptingInstr) i).getClosureArg(); if (o != null && o instanceof WrappedIRClosure) { IRClosure cl = ((WrappedIRClosure) o).getClosure(); // Add before call -- hence instrs.previous & instrs.next instrs.previous(); // If the call is a dataflow barrier, we have to spill everything here boolean spillAllVars = scopeBindingHasEscaped; // Unless we have to spill everything, spill only those dirty variables that are: // - used in the closure (FIXME: Strictly only those vars that are live at the call site // -- but we dont have this info!) Set<LocalVariable> newDirtyVars = new HashSet<LocalVariable>(dirtyVars); for (LocalVariable v : dirtyVars) { // We have to spill the var that is defined in the closure as well because the load var // pass // will attempt to load the var always. So, if the call doesn't actually call the // closure, // we'll be in trouble in that scenario! if (spillAllVars || cl.usesLocalVariable(v) || cl.definesLocalVariable(v)) { addedStores = true; instrs.add( new StoreLocalVarInstr( problem.getLocalVarReplacement(v, varRenameMap), scope, v)); newDirtyVars.remove(v); } } dirtyVars = newDirtyVars; instrs.next(); } else if (scopeBindingHasEscaped) { // Call has no closure && it requires stores // Add before call -- hence instrs.previous & instrs.next instrs.previous(); for (LocalVariable v : dirtyVars) { addedStores = true; instrs.add( new StoreLocalVarInstr(problem.getLocalVarReplacement(v, varRenameMap), scope, v)); } instrs.next(); dirtyVars.clear(); } else { instrs.previous(); // All variables not local to the current scope have to be always spilled because of // multi-threading scenarios where some other scope could load this variable concurrently. // // Allocate a new hash-set and modify it to get around ConcurrentModificationException on // dirtyVars Set<LocalVariable> newDirtyVars = new HashSet<LocalVariable>(dirtyVars); for (LocalVariable v : dirtyVars) { // SSS FIXME: I guess we cannot use v.getScopeDepth() > 0 because the variable could be // a cloned // instance from a different depth and that could mislead us. See if there is a way to // fix this. // If we introduced 'definingScope' in all local variables, we could simply check for // scope match // without the instanceof check here. if ((v instanceof ClosureLocalVariable && ((ClosureLocalVariable) v).definingScope != scope) || (!(v instanceof ClosureLocalVariable) && scope.getScopeType().isClosureType())) { addedStores = true; instrs.add( new StoreLocalVarInstr( problem.getLocalVarReplacement(v, varRenameMap), scope, v)); newDirtyVars.remove(v); } } dirtyVars = newDirtyVars; instrs.next(); } } else if ((isClosure && (i instanceof ReturnInstr)) || (i instanceof BreakInstr)) { // At closure return and break instructions (both of which are exits from the closure), // we need a binding store on exit only for vars that are both: // // (a) dirty, // (b) live on exit from the closure // condition reqd. because the variable could be dirty but not used outside. // Ex: s=0; a.each { |i| j = i+1; sum += j; return if j < 5; sum += 1; }; puts sum // i,j are dirty inside the block, but not used outside // // If this also happens to be exit BB, we would have intersected already earlier -- so no // need to do it again! if (!basicBlock.isExitBB()) { LiveVariablesProblem lvp = (LiveVariablesProblem) scope.getDataFlowSolution(DataFlowConstants.LVP_NAME); if (lvp == null) { System.out.println("LVP missing for " + scope); } java.util.Collection<LocalVariable> liveVars = lvp.getVarsLiveOnScopeExit(); if (liveVars != null) { dirtyVars.retainAll( liveVars); // Intersection with variables live on exit from the scope } else { dirtyVars.clear(); } } // Add before call instrs.previous(); boolean f = problem.addClosureExitStoreLocalVars(instrs, dirtyVars, varRenameMap); addedStores = addedStores || f; instrs.next(); // Nothing is dirty anymore -- everything that needs spilling has been spilt dirtyVars.clear(); } if (scopeBindingHasEscaped && (i.getOperation() == Operation.PUT_GLOBAL_VAR)) { // global-var tracing can execute closures set up in previous trace-var calls // in which case we would have the 'scopeBindingHasEscaped' flag set to true instrs.previous(); for (LocalVariable v : dirtyVars) { addedStores = true; instrs.add( new StoreLocalVarInstr(problem.getLocalVarReplacement(v, varRenameMap), scope, v)); } instrs.next(); dirtyVars.clear(); } if (i.canRaiseException()) { if (hasExceptionsRescued()) { // If exceptions will be rescued, spill every dirty var here // Add before excepting instr -- hence instrs.previous & instrs.next instrs.previous(); for (LocalVariable v : dirtyVars) { addedStores = true; instrs.add( new StoreLocalVarInstr(problem.getLocalVarReplacement(v, varRenameMap), scope, v)); } instrs.next(); dirtyVars.clear(); } else if (excTargetDirtyVars != null) { // If exceptions won't be rescued, pass them back to be spilled in the global ensure block excTargetDirtyVars.addAll(dirtyVars); } } if (i instanceof ResultInstr) { Variable v = ((ResultInstr) i).getResult(); // %self is local to every scope and never crosses scope boundaries and need not be // spilled/refilled if (v instanceof LocalVariable && !v.isSelf()) { LocalVariable lv = (LocalVariable) v; dirtyVars.add(lv); // Make sure there is a replacement tmp-var allocated for lv problem.getLocalVarReplacement(lv, varRenameMap); } } } // If this is the exit BB, add binding stores for all vars that are still dirty if (basicBlock.isExitBB()) { // Last instr could be a return -- so, move iterator one position back if (instrs.hasPrevious()) instrs.previous(); boolean f = problem.addClosureExitStoreLocalVars(instrs, dirtyVars, varRenameMap); addedStores = addedStores || f; } return addedStores; }