private void updateUnboxedVarsInfo( Instr i, UnboxState state, Variable dst, boolean hasRescuer, boolean isDFBarrier) { HashSet<Variable> varsToBox = new HashSet<Variable>(); // Special treatment for instructions that can raise exceptions if (i.canRaiseException()) { if (hasRescuer) { // If we are going to be rescued, // box all unboxed dirty vars before we execute the instr. state.unboxedDirtyVars.clear(); } else { // We are going to exit if an exception is raised. // So, only need to bother with dirty live local vars for closures if (problem.getScope() instanceof IRClosure) { markLocalVariables(varsToBox, state.unboxedDirtyVars); } } } if (isDFBarrier) { // All dirty unboxed local vars will get reboxed. markLocalVariables(varsToBox, state.unboxedDirtyVars); // We have to re-unbox local variables as necessary since we don't // know how they are going to change once we get past this instruction. List<Variable> lvs = new ArrayList<Variable>(); markLocalVariables(lvs, state.unboxedVars.keySet()); state.unboxedVars.keySet().removeAll(lvs); } // Update set of unboxed dirty vars state.unboxedDirtyVars.removeAll(varsToBox); // FIXME: Also global variables .. see LVA / StoreLocalVar analysis. // B_TRUE and B_FALSE have unboxed forms and their operands // needn't get boxed back. Operation op = i.getOperation(); if (op != Operation.B_TRUE && op != Operation.B_FALSE) { // Vars used by this instruction that only exist in unboxed form // will have to get boxed before it is executed state.unboxedDirtyVars.removeAll(i.getUsedVariables()); } // If the instruction writes into 'dst', it will be in boxed form. if (dst != null) { state.unboxedVars.remove(dst); state.unboxedDirtyVars.remove(dst); } }
private void boxRequiredVars( Instr i, UnboxState state, Map<Variable, TemporaryLocalVariable> unboxMap, Variable dst, boolean hasRescuer, boolean isDFBarrier, List<Instr> newInstrs) { // Special treatment for instructions that can raise exceptions boolean isClosure = this.problem.getScope() instanceof IRClosure; HashSet<Variable> varsToBox = new HashSet<Variable>(); if (i.canRaiseException()) { if (hasRescuer) { // If we are going to be rescued, // box all unboxed dirty vars before we execute the instr varsToBox.addAll(state.unboxedDirtyVars); } else if (isClosure) { // We are going to exit if an exception is raised. // So, only need to bother with dirty live local vars for closures for (Variable v : state.unboxedDirtyVars) { if (v instanceof LocalVariable) { varsToBox.add(v); } } } } if (isClosure && (i instanceof ReturnInstr || i instanceof BreakInstr)) { for (Variable v : state.unboxedDirtyVars) { if (v instanceof LocalVariable) { varsToBox.add(v); } } } if (isDFBarrier) { // All dirty unboxed (local) vars will get reboxed. for (Variable v : state.unboxedDirtyVars) { if (v instanceof LocalVariable) { varsToBox.add(v); } } // We have to re-unbox local variables as necessary since we don't // know how they are going to change once we get past this instruction. List<Variable> lvs = new ArrayList<Variable>(); for (Variable v : state.unboxedVars) { if (v instanceof LocalVariable) { lvs.add(v); } } state.unboxedVars.removeAll(lvs); } // B_TRUE and B_FALSE have unboxed forms and their operands // needn't get boxed back. Operation op = i.getOperation(); boolean isBranch = op == Operation.B_TRUE || op == Operation.B_FALSE; if (!isBranch) { // Vars used by this instruction that only exist in unboxed form // will have to get boxed before it is executed for (Variable v : i.getUsedVariables()) { if (state.unboxedDirtyVars.contains(v)) { varsToBox.add(v); } } } // Add boxing instrs. for (Variable v : varsToBox) { newInstrs.add(new BoxFloatInstr(v, getUnboxedVar(unboxMap, v))); state.unboxedDirtyVars.remove(v); } // Add 'i' itself if (isBranch) { OneOperandBranchInstr bi = (OneOperandBranchInstr) i; Operand a = bi.getArg1(); Operand ua = getUnboxedOperand(state.unboxedVars, unboxMap, a, newInstrs, false); if (ua == a) { newInstrs.add(i); } else if (op == Operation.B_TRUE) { newInstrs.add(new BTrueInstr(Operation.B_TRUE_UNBOXED, ua, bi.getJumpTarget())); } else { newInstrs.add(new BFalseInstr(Operation.B_FALSE_UNBOXED, ua, bi.getJumpTarget())); } } else { newInstrs.add(i); } // If the instruction writes into 'dst', it will be in boxed form. if (dst != null) { state.unboxedVars.remove(dst); state.unboxedDirtyVars.remove(dst); } }
@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(); }
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; }