@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; }
@Override public Object previouslyRun(IRScope scope) { return scope.getDataFlowSolution(StoreLocalVarPlacementProblem.NAME); }
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; }