// // This can help use eliminate writes to %block that are not used since this is // a special local-variable, not programmer-defined local-variable public void computeScopeFlags() { if (flagsComputed) { return; } // init canModifyCode = true; canCaptureCallersBinding = false; usesZSuper = false; usesEval = false; usesBackrefOrLastline = false; // NOTE: bindingHasEscaped is the crucial flag and it effectively is // unconditionally true whenever it has a call that receives a closure. // See CallInstr.computeRequiresCallersBindingFlag bindingHasEscaped = (this instanceof IREvalScript); // for eval scopes, bindings are considered escaped ... hasBreakInstrs = false; hasNonlocalReturns = false; canReceiveBreaks = false; canReceiveNonlocalReturns = false; // recompute flags -- we could be calling this method different times // definitely once after ir generation and local optimizations propagates constants locally // but potentially at a later time after doing ssa generation and constant propagation if (cfg == null) { computeScopeFlags(false, getInstrs()); } else { boolean receivesClosureArg = false; for (BasicBlock b : cfg.getBasicBlocks()) { receivesClosureArg = computeScopeFlags(receivesClosureArg, b.getInstrs()); } } // Compute flags for nested closures (recursively) and set derived flags. for (IRClosure cl : getClosures()) { cl.computeScopeFlags(); if (cl.hasBreakInstrs || cl.canReceiveBreaks) { canReceiveBreaks = true; } if (cl.hasNonlocalReturns || cl.canReceiveNonlocalReturns) { canReceiveNonlocalReturns = true; } if (cl.usesZSuper()) { usesZSuper = true; } } flagsComputed = 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; }