public static IRubyObject INTERPRET_METHOD( ThreadContext context, InterpretedIRMethod irMethod, IRubyObject self, String name, IRubyObject[] args, Block block, Block.Type blockType, boolean isTraceable) { Ruby runtime = context.runtime; IRScope scope = irMethod.getIRMethod(); RubyModule implClass = irMethod.getImplementationClass(); Visibility viz = irMethod.getVisibility(); boolean syntheticMethod = name == null || name.equals(""); try { if (!syntheticMethod) ThreadContext.pushBacktrace(context, name, scope.getFileName(), context.getLine()); if (isTraceable) methodPreTrace(runtime, context, name, implClass); return interpret(context, self, scope, viz, implClass, args, block, blockType); } finally { if (isTraceable) { try { methodPostTrace(runtime, context, name, implClass); } finally { if (!syntheticMethod) ThreadContext.popBacktrace(context); } } else { if (!syntheticMethod) ThreadContext.popBacktrace(context); } } }
@Override public boolean computeScopeFlags(IRScope scope) { boolean modifiedScope = false; if (targetRequiresCallersBinding()) { modifiedScope = true; scope.getFlags().add(BINDING_HAS_ESCAPED); } if (targetRequiresCallersFrame()) { modifiedScope = true; scope.getFlags().add(REQUIRES_FRAME); } if (canBeEval()) { modifiedScope = true; scope.getFlags().add(USES_EVAL); // If this method receives a closure arg, and this call is an eval that has more than 1 // argument, // it could be using the closure as a binding -- which means it could be using pretty much any // variable from the caller's binding! if (scope.getFlags().contains(RECEIVES_CLOSURE_ARG) && (getCallArgs().length > 1)) { scope.getFlags().add(CAN_CAPTURE_CALLERS_BINDING); } } return modifiedScope; }
public boolean isNestedInClosure(IRClosure closure) { for (IRScope s = this; s != null && !s.isTopLocalVariableScope(); s = s.getLexicalParent()) { if (s == closure) return true; } return false; }
/** Returns the top level scope */ public IRScope getTopLevelScope() { IRScope current = this; for (; current != null && !current.isScriptScope(); current = current.getLexicalParent()) {} return current; }
public IRScope getNearestTopLocalVariableScope() { IRScope current = this; while (current != null && !current.isTopLocalVariableScope()) { current = current.getLexicalParent(); } return current; }
public IRMethod getNearestMethod() { IRScope current = this; while (current != null && !(current instanceof IRMethod)) { current = current.getLexicalParent(); } return (IRMethod) current; }
public void applyPreMeetHandler() { IRScope s = problem.getScope(); if (s instanceof IRClosure && basicBlock == s.getCFG().getEntryBB()) { // If it is not null, it has already been initialized if (inState == null) { inState = new UnboxState(); } } else { inState = new UnboxState(); } }
protected void doDebug() { // FIXME: This is printing out IRScope CFG but JIT may be active and it might not reflect // currently executing. Move into JIT and into interp since they will be getting CFG from // different sources // FIXME: This is only printing out CFG once. If we keep applying more passes then we // will want to print out after those new passes. ensureInstrsReady(); LOG.info("Executing '" + method.getName() + "'"); if (!displayedCFG) { LOG.info(method.debugOutput()); displayedCFG = true; } }
/** * 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; }
private boolean doesItRequireFrame(IRScope scope, boolean bindingHasEscaped) { boolean requireFrame = bindingHasEscaped || scope.usesEval(); for (IRFlags flag : scope.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; }
private TemporaryLocalVariable getLocalVarReplacement( LocalVariable v, IRScope scope, Map<Operand, Operand> varRenameMap) { TemporaryLocalVariable value = (TemporaryLocalVariable) varRenameMap.get(v); if (value == null) { value = scope.getNewTemporaryVariableFor(v); varRenameMap.put(v, value); } return value; }
public IREvalScript( IRManager manager, IRScope lexicalParent, String fileName, int lineNumber, StaticScope staticScope) { super(manager, lexicalParent, fileName, lineNumber, staticScope, "EVAL_"); int n = 0; IRScope s = lexicalParent; while (s instanceof IREvalScript) { n++; s = s.getLexicalParent(); } this.nearestNonEvalScope = s; this.nearestNonEvalScopeDepth = n; this.nearestNonEvalScope.initEvalScopeVariableAllocator(false); }
@Override public LocalVariable findExistingLocalVariable(String name, int scopeDepth) { // Look in the nearest non-eval scope's shared eval scope vars first. // If you dont find anything there, look in the nearest non-eval scope's regular vars. LocalVariable lvar = lookupExistingLVar(name); if (lvar != null || scopeDepth == 0) return lvar; else return nearestNonEvalScope.findExistingLocalVariable( name, scopeDepth - nearestNonEvalScopeDepth - 1); }
public MixedModeIRMethod(IRScope method, Visibility visibility, RubyModule implementationClass) { super(implementationClass, visibility, CallConfiguration.FrameNoneScopeNone, method.getName()); this.method = method; getStaticScope().determineModule(); this.signature = getStaticScope().getSignature(); // disable JIT if JIT is disabled if (!implementationClass.getRuntime().getInstanceConfig().getCompileMode().shouldJIT()) { this.box.callCount = -1; } }
public void addDFVar(Variable v) { Integer dfv = addDataFlowVar(); dfVarMap.put(v, dfv); varDfVarMap.put(dfv, v); if (v instanceof LocalVariable && !v.isSelf()) { // System.out.println("Adding df var for " + v + ":" + dfv.id); IRScope s = getScope(); for (int n = ((LocalVariable) v).getScopeDepth(); s != null && n >= 0; n--) { if (s instanceof IREvalScript) { // If a variable is at the topmost scope of the eval OR crosses an eval boundary, // it is going to be marked always live since it could be used by other evals (n = 0) // or by enclosing scopes (n > 0) alwaysLiveVars.add((LocalVariable) v); break; } s = s.getLexicalParent(); } localVars.add((LocalVariable) v); } }
public static IRubyObject INTERPRET_BLOCK( ThreadContext context, IRubyObject self, IRScope scope, IRubyObject[] args, String name, Block block, Block.Type blockType) { try { ThreadContext.pushBacktrace(context, name, scope.getFileName(), context.getLine()); return interpret(context, self, scope, null, null, args, block, blockType); } finally { ThreadContext.popBacktrace(context); } }
@Override public Object execute(IRScope s, Object... data) { StoreLocalVarPlacementProblem slvp = new StoreLocalVarPlacementProblem(); // Only run if we are pushing a scope or we are reusing the parents scope. if (!s.getFlags().contains(IRFlags.DYNSCOPE_ELIMINATED) || s.getFlags().contains(IRFlags.REUSE_PARENT_DYNSCOPE)) { // Make sure flags are computed s.computeScopeFlags(); Map<Operand, Operand> varRenameMap = new HashMap<Operand, Operand>(); // 1. Figure out required stores // 2. Add stores // 3. Figure out required loads // 4. Add loads // // Order is important since loads in 3. depend on stores in 2. slvp.setup(s); slvp.compute_MOP_Solution(); // Add stores, assigning an equivalent tmp-var for each local var slvp.addStores(varRenameMap); // Once stores have been added, figure out required loads LoadLocalVarPlacementProblem llvp = new LoadLocalVarPlacementProblem(); llvp.setup(s); llvp.compute_MOP_Solution(); // Add loads llvp.addLoads(varRenameMap); // Rename all local var uses with their tmp-var stand-ins for (BasicBlock b : s.getCFG().getBasicBlocks()) { for (Instr i : b.getInstrs()) i.renameVars(varRenameMap); } // Run on all nested closures. // // In the current implementation, nested scopes are processed independently (unlike Live // Variable Analysis) for (IRClosure c : s.getClosures()) run(c, false, true); // LVA information is no longer valid after this pass // FIXME: Grrr ... this seems broken to have to create a new object to invalidate (new LiveVariableAnalysis()).invalidate(s); } s.setDataFlowSolution(StoreLocalVarPlacementProblem.NAME, slvp); return slvp; }
@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)); } } } } }
private static IRubyObject interpret( ThreadContext context, IRubyObject self, IRScope scope, Visibility visibility, RubyModule implClass, IRubyObject[] args, Block block, Block.Type blockType) { Instr[] instrs = scope.getInstrsForInterpretation(); // The base IR may not have been processed yet if (instrs == null) instrs = scope.prepareForInterpretation(blockType == Block.Type.LAMBDA); int numTempVars = scope.getTemporaryVariableSize(); Object[] temp = numTempVars > 0 ? new Object[numTempVars] : null; int n = instrs.length; int ipc = 0; Instr instr = null; Object exception = null; int kwArgHashCount = (scope.receivesKeywordArgs() && args[args.length - 1] instanceof RubyHash) ? 1 : 0; DynamicScope currDynScope = context.getCurrentScope(); // Counter tpCount = null; // Init profiling this scope boolean debug = IRRuntimeHelpers.isDebug(); boolean profile = IRRuntimeHelpers.inProfileMode(); Integer scopeVersion = profile ? initProfiling(scope) : 0; // Enter the looooop! while (ipc < n) { instr = instrs[ipc]; ipc++; Operation operation = instr.getOperation(); if (debug) { LOG.info("I: {}", instr); interpInstrsCount++; } else if (profile) { if (operation.modifiesCode()) codeModificationsCount++; interpInstrsCount++; /* Counter cnt = opStats.get(operation); if (cnt == null) { cnt = new Counter(); opStats.put(operation, cnt); } cnt.count++; */ } try { switch (operation.opClass) { case ARG_OP: { receiveArg( context, instr, operation, args, kwArgHashCount, currDynScope, temp, exception, block); break; } case BRANCH_OP: { if (operation == Operation.JUMP) { ipc = ((JumpInstr) instr).getJumpTarget().getTargetPC(); } else { ipc = instr.interpretAndGetNewIPC(context, currDynScope, self, temp, ipc); } break; } case CALL_OP: { if (profile) updateCallSite(instr, scope, scopeVersion); processCall( context, instr, operation, scope, currDynScope, temp, self, block, blockType); break; } case BOOK_KEEPING_OP: { switch (operation) { case PUSH_FRAME: { context.preMethodFrameAndClass( implClass, scope.getName(), self, block, scope.getStaticScope()); context.setCurrentVisibility(visibility); break; } case PUSH_BINDING: { // SSS NOTE: Method scopes only! // // Blocks are a headache -- so, these instrs. are only added to IRMethods. // Blocks have more complicated logic for pushing a dynamic scope (see // InterpretedIRBlockBody) // Changed by DPR currDynScope = DynamicScope.newDynamicScope( scope.getStaticScope(), context.getCurrentScope().getDepth()); context.pushScope(currDynScope); break; } case CHECK_ARITY: ((CheckArityInstr) instr).checkArity(context.runtime, args.length); break; case POP_FRAME: context.popFrame(); context.popRubyClass(); break; case POP_BINDING: context.popScope(); break; case THREAD_POLL: if (profile) { // SSS: Not being used currently // tpCount.count++; globalThreadPollCount++; // 20K is arbitrary // Every 20K profile counts, spit out profile stats if (globalThreadPollCount % 20000 == 0) { analyzeProfile(); // outputProfileStats(); } } context.callThreadPoll(); break; case LINE_NUM: context.setLine(((LineNumberInstr) instr).lineNumber); break; case RECORD_END_BLOCK: ((RecordEndBlockInstr) instr).interpret(); break; } break; } case OTHER_OP: { Object result = null; switch (operation) { // --------- Return flavored instructions -------- case BREAK: { BreakInstr bi = (BreakInstr) instr; IRubyObject rv = (IRubyObject) bi.getReturnValue().retrieve(context, self, currDynScope, temp); // This also handles breaks in lambdas -- by converting them to a return return IRRuntimeHelpers.initiateBreak( context, scope, bi.getScopeToReturnTo().getScopeId(), rv, blockType); } case RETURN: { return (IRubyObject) retrieveOp( ((ReturnBase) instr).getReturnValue(), context, self, currDynScope, temp); } case NONLOCAL_RETURN: { NonlocalReturnInstr ri = (NonlocalReturnInstr) instr; IRubyObject rv = (IRubyObject) retrieveOp(ri.getReturnValue(), context, self, currDynScope, temp); ipc = n; // If not in a lambda, check if this was a non-local return if (!IRRuntimeHelpers.inLambda(blockType)) { IRRuntimeHelpers.initiateNonLocalReturn( context, scope, ri.methodToReturnFrom, rv); } return rv; } // ---------- Common instruction --------- case COPY: { CopyInstr c = (CopyInstr) instr; result = retrieveOp(c.getSource(), context, self, currDynScope, temp); setResult(temp, currDynScope, c.getResult(), result); break; } case GET_FIELD: { GetFieldInstr gfi = (GetFieldInstr) instr; IRubyObject object = (IRubyObject) gfi.getSource().retrieve(context, self, currDynScope, temp); VariableAccessor a = gfi.getAccessor(object); result = a == null ? null : (IRubyObject) a.get(object); if (result == null) { result = context.nil; } setResult(temp, currDynScope, gfi.getResult(), result); break; } case SEARCH_CONST: { SearchConstInstr sci = (SearchConstInstr) instr; result = sci.getCachedConst(); if (!sci.isCached(context, result)) result = sci.cache(context, currDynScope, self, temp); setResult(temp, currDynScope, sci.getResult(), result); break; } // ---------- All the rest --------- default: result = instr.interpret(context, currDynScope, self, temp, block); setResult(temp, currDynScope, instr, result); break; } break; } } } catch (Throwable t) { // Unrescuable: // IRReturnJump, ThreadKill, RubyContinuation, MainExitException, etc. // These cannot be rescued -- only run ensure blocks // // Others: // IRBreakJump, Ruby exceptions, errors, and other java exceptions. // These can be rescued -- run rescue blocks if (debug) LOG.info( "in scope: " + scope + ", caught Java throwable: " + t + "; excepting instr: " + instr); ipc = (t instanceof Unrescuable) ? scope.getEnsurerPC(instr) : scope.getRescuerPC(instr); if (debug) LOG.info("ipc for rescuer/ensurer: " + ipc); if (ipc == -1) { Helpers.throwException((Throwable) t); } else { exception = t; } } } // Control should never get here! // SSS FIXME: But looks like BEGIN/END blocks get here -- needs fixing return null; }
public StaticScope getStaticScope() { return method.getStaticScope(); }
public int getLine() { return method.getLineNumber(); }
public String getFile() { return method.getFileName(); }
public Object execute(IRScope scope, Object... data) { scope.buildLinearization(); return null; }
// FIXME: for subclasses we should override this method since it can be simple get // FIXME: to avoid cost of synch call in lazilyacquire we can save the ic here public InterpreterContext ensureInstrsReady() { if (method instanceof IRMethod) { return ((IRMethod) method).lazilyAcquireInterpreterContext(); } return method.getInterpreterContext(); }
@Override public boolean invalidate(IRScope scope) { super.invalidate(scope); scope.setDataFlowSolution(StoreLocalVarPlacementProblem.NAME, null); return true; }
@Override public Object previouslyRun(IRScope scope) { return scope.getDataFlowSolution(StoreLocalVarPlacementProblem.NAME); }
public BeginEndInterpreterContext(IRScope scope, Instr[] instructions, boolean rebuild) { super(scope, instructions, rebuild); beginBlocks = scope.getBeginBlocks(); }
@Override public boolean computeScopeFlags(IRScope scope) { scope.getFlags().add(IRFlags.RECEIVES_CLOSURE_ARG); return true; }
@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; }