public override Expression Reduce() { return(Expression.Condition( Expression.Block( Expression.Assign(LightExceptionRewriter._lastValue, _expr), IsLightExceptionExpression.Instance ), Expression.Goto(_target, _lastValue, _retType), Utils.Convert(LightExceptionRewriter._lastValue, _retType) )); }
private Expression PropagateException(Type retType) { if (_currentHandler == null) { // no eh blocks, we propagate the value up return(Expression.Goto(_returnLabel, _lastValue, retType)); } // branch to current exception handler return(Expression.Goto(_currentHandler, retType)); }
// Skip the finally block if we are yielding, but not if we're doing a // yield break private Expression MakeSkipFinallyBlock(LabelTarget target) { return(Expression.Condition( Expression.AndAlso( Expression.Equal(_gotoRouter, AstUtils.Constant(GotoRouterYielding)), Expression.NotEqual(_state, AstUtils.Constant(Finished)) ), Expression.Goto(target), Utils.Empty() )); }
private SwitchExpression MakeYieldRouter(Type type, int start, int end, LabelTarget newTarget) { Debug.Assert(end > start); var cases = new SwitchCase[end - start]; for (int i = start; i < end; i++) { YieldMarker y = _yields[i]; cases[i - start] = Expression.SwitchCase(Expression.Goto(y.Label, type), AstUtils.Constant(y.State)); // Any jumps from outer switch statements should go to the this // router, not the original label (which they cannot legally jump to) y.Label = newTarget; } return(Expression.Switch(_gotoRouter, Expression.Default(type), cases)); }
protected override Expression VisitGoto(GotoExpression node) { if (node.Target == _target) { return(Expression.Goto( _gotoInfo.VoidTarget, Expression.Block( _rewriter.MakeAssign(_gotoInfo.Variable, node.Value), Expression.Default(typeof(void)) ), node.Type )); } return(base.VisitGoto(node)); }
private Expression VisitYield(YieldExpression node) { if (node.Target != _generator.Target) { throw new InvalidOperationException("yield and generator must have the same LabelTarget object"); } var value = Visit(node.Value); var block = new ReadOnlyCollectionBuilder <Expression>(); if (value == null) { // Yield break block.Add(Expression.Assign(_state, AstUtils.Constant(Finished))); if (_inTryWithFinally) { block.Add(Expression.Assign(_gotoRouter, AstUtils.Constant(GotoRouterYielding))); } block.Add(Expression.Goto(_returnLabels.Peek())); return(Expression.Block(block)); } // Yield return block.Add(MakeAssign(_current, value)); YieldMarker marker = GetYieldMarker(node); block.Add(Expression.Assign(_state, AstUtils.Constant(marker.State))); if (_inTryWithFinally) { block.Add(Expression.Assign(_gotoRouter, AstUtils.Constant(GotoRouterYielding))); } block.Add(Expression.Goto(_returnLabels.Peek())); block.Add(Expression.Label(marker.Label)); block.Add(Expression.Assign(_gotoRouter, AstUtils.Constant(GotoRouterNone))); block.Add(Utils.Empty()); return(Expression.Block(block)); }
// Determine if we can break directly to the label, or if we need to dispatch again // If we're breaking directly, we reset the _flowVariable, otherwise we just jump to // the next FlowLabel private Expression MakeFlowJump(LabelTarget target) { foreach (var block in _blocks) { if (block.LabelDefs.Contains(target)) { break; } if (block.InFinally || block.HasFlow) { EnsureFlow(block); block.NeedFlowLabels.Add(target); // If we need to go through another finally, just jump // to its flow label return(Expression.Goto(block.FlowLabel)); } } // Got here without needing flow, reset the flag and emit the real goto return(Expression.Block( Expression.Assign(_flowVariable, AstUtils.Constant(0)), Expression.Goto(target, _labels[target].Variable) )); }
internal Expression Reduce() { // Visit body Expression body = Visit(_generator.Body); Debug.Assert(_returnLabels.Count == 1); // Add the switch statement to the body int count = _yields.Count; var cases = new SwitchCase[count + 1]; for (int i = 0; i < count; i++) { cases[i] = Expression.SwitchCase(Expression.Goto(_yields[i].Label), AstUtils.Constant(_yields[i].State)); } cases[count] = Expression.SwitchCase(Expression.Goto(_returnLabels.Peek()), AstUtils.Constant(Finished)); Type generatorNextOfT = typeof(GeneratorNext <>).MakeGenericType(_generator.Target.Type); // Create the lambda for the GeneratorNext<T>, hoisting variables // into a scope outside the lambda var allVars = new List <ParameterExpression>(_vars); allVars.AddRange(_temps); // Collect temps that don't have to be closed over var innerTemps = new ReadOnlyCollectionBuilder <ParameterExpression>(1 + (_labelTemps?.Count ?? 0)); innerTemps.Add(_gotoRouter); if (_labelTemps != null) { foreach (LabelInfo info in _labelTemps.Values) { innerTemps.Add(info.Temp); } } body = Expression.Block( allVars, Expression.Lambda( generatorNextOfT, Expression.Block( innerTemps, Expression.Switch(Expression.Assign(_gotoRouter, _state), cases), body, Expression.Assign(_state, AstUtils.Constant(Finished)), Expression.Label(_returnLabels.Peek()) ), _generator.Name, new ParameterExpression[] { _state, _current } ) ); // Enumerable factory takes Func<GeneratorNext<T>> instead of GeneratorNext<T> if (_generator.IsEnumerable) { body = Expression.Lambda(body); } // We can't create a ConstantExpression of _debugCookies array here because we walk the tree // after constants have already been rewritten. Instead we create a NewArrayExpression node // which initializes the array with contents from _debugCookies Expression debugCookiesArray = null; if (_debugCookies != null) { Expression[] debugCookies = new Expression[_debugCookies.Count]; for (int i = 0; i < _debugCookies.Count; i++) { debugCookies[i] = AstUtils.Constant(_debugCookies[i]); } debugCookiesArray = Expression.NewArrayInit( typeof(int), debugCookies); } // Generate a call to ScriptingRuntimeHelpers.MakeGenerator<T>(args) return(Expression.Call( typeof(ScriptingRuntimeHelpers), "MakeGenerator", new[] { _generator.Target.Type }, (debugCookiesArray != null) ? new[] { body, debugCookiesArray } : new[] { body } )); }
protected override Expression VisitTry(TryExpression node) { int startYields = _yields.Count; bool savedInTryWithFinally = _inTryWithFinally; if (node.Finally != null || node.Fault != null) { _inTryWithFinally = true; } Expression @try = Visit(node.Body); int tryYields = _yields.Count; IList <CatchBlock> handlers = Visit(node.Handlers, VisitCatchBlock); int catchYields = _yields.Count; // push a new return label in case the finally block yields _returnLabels.Push(Expression.Label()); // only one of these can be non-null Expression @finally = Visit(node.Finally); Expression fault = Visit(node.Fault); LabelTarget finallyReturn = _returnLabels.Pop(); int finallyYields = _yields.Count; _inTryWithFinally = savedInTryWithFinally; if (@try == node.Body && handlers == node.Handlers && @finally == node.Finally && fault == node.Fault) { return(node); } // No yields, just return if (startYields == _yields.Count) { return(Expression.MakeTry(null, @try, @finally, fault, handlers)); } if (fault != null && finallyYields != catchYields) { // No one needs this yet, and it's not clear how we should get back to // the fault throw new NotSupportedException("yield in fault block is not supported"); } // If try has yields, we need to build a new try body that // dispatches to the yield labels var tryStart = Expression.Label(); if (tryYields != startYields) { @try = Expression.Block(MakeYieldRouter(node.Body.Type, startYields, tryYields, tryStart), @try); } // Transform catches with yield to deferred handlers if (catchYields != tryYields) { var block = new List <Expression>(); block.Add(MakeYieldRouter(node.Body.Type, tryYields, catchYields, tryStart)); block.Add(null); // empty slot to fill in later for (int i = 0, n = handlers.Count; i < n; i++) { CatchBlock c = handlers[i]; if (c == node.Handlers[i]) { continue; } if (handlers.IsReadOnly) { handlers = handlers.ToArray(); } // the variable that will be scoped to the catch block var exceptionVar = Expression.Variable(c.Test, null); // the variable that the catch block body will use to // access the exception. We reuse the original variable if // the catch block had one. It needs to be hoisted because // the catch might contain yields. var deferredVar = c.Variable ?? Expression.Variable(c.Test, null); _vars.Add(deferredVar); // We need to ensure that filters can access the exception // variable Expression filter = c.Filter; if (filter != null && c.Variable != null) { filter = Expression.Block(new[] { c.Variable }, Expression.Assign(c.Variable, exceptionVar), filter); } // catch (ExceptionType exceptionVar) { // deferredVar = exceptionVar; // } handlers[i] = Expression.Catch( exceptionVar, Expression.Block( Expression.Assign(deferredVar, exceptionVar), Expression.Default(node.Body.Type) ), filter ); // We need to rewrite rethrows into "throw deferredVar" var catchBody = new RethrowRewriter { Exception = deferredVar }.Visit(c.Body); // if (deferredVar != null) { // ... catch body ... // } block.Add( Expression.Condition( Expression.NotEqual(deferredVar, AstUtils.Constant(null, deferredVar.Type)), catchBody, Expression.Default(node.Body.Type) ) ); } block[1] = Expression.MakeTry(null, @try, null, null, new ReadOnlyCollection <CatchBlock>(handlers)); @try = Expression.Block(block); handlers = new CatchBlock[0]; // so we don't reuse these } if (finallyYields != catchYields) { // We need to add a catch block to save the exception, so we // can rethrow in case there is a yield in the finally. Also, // add logic for returning. It looks like this: // // try { ... } catch (Exception all) { saved = all; } // finally { // if (_finallyReturnVar) goto finallyReturn; // ... // if (saved != null) throw saved; // finallyReturn: // } // if (_finallyReturnVar) goto _return; // We need to add a catch(Exception), so if we have catches, // wrap them in a try if (handlers.Count > 0) { @try = Expression.MakeTry(null, @try, null, null, handlers); handlers = new CatchBlock[0]; } // NOTE: the order of these routers is important // The first call changes the labels to all point at "tryEnd", // so the second router will jump to "tryEnd" var tryEnd = Expression.Label(); Expression inFinallyRouter = MakeYieldRouter(node.Body.Type, catchYields, finallyYields, tryEnd); Expression inTryRouter = MakeYieldRouter(node.Body.Type, catchYields, finallyYields, tryStart); var all = Expression.Variable(typeof(Exception), "e"); var saved = Expression.Variable(typeof(Exception), "$saved$" + _temps.Count); _temps.Add(saved); @try = Expression.Block( Expression.TryCatchFinally( Expression.Block( inTryRouter, @try, Expression.Assign(saved, AstUtils.Constant(null, saved.Type)), Expression.Label(tryEnd) ), Expression.Block( MakeSkipFinallyBlock(finallyReturn), inFinallyRouter, @finally, Expression.Condition( Expression.NotEqual(saved, AstUtils.Constant(null, saved.Type)), Expression.Throw(saved), Utils.Empty() ), Expression.Label(finallyReturn) ), Expression.Catch(all, Utils.Void(Expression.Assign(saved, all))) ), Expression.Condition( Expression.Equal(_gotoRouter, AstUtils.Constant(GotoRouterYielding)), Expression.Goto(_returnLabels.Peek()), Utils.Empty() ) ); @finally = null; } else if (@finally != null) { // try or catch had a yield, modify finally so we can skip over it @finally = Expression.Block( MakeSkipFinallyBlock(finallyReturn), @finally, Expression.Label(finallyReturn) ); } // Make the outer try, if needed if (handlers.Count > 0 || @finally != null || fault != null) { @try = Expression.MakeTry(null, @try, @finally, fault, handlers); } return(Expression.Block(Expression.Label(tryStart), @try)); }
protected override Expression VisitTry(TryExpression node) { int startYields = _yields.Count; bool savedInTryWithFinally = _inTryWithFinally; if (node.Finally != null || node.Fault != null) { _inTryWithFinally = true; } Expression @try = Visit(node.Body); int tryYields = _yields.Count; IList <CatchBlock> handlers = Visit(node.Handlers, VisitCatchBlock); int catchYields = _yields.Count; // push a new return label in case the finally block yields _returnLabels.Push(Expression.Label()); // only one of these can be non-null Expression @finally = Visit(node.Finally); Expression fault = Visit(node.Fault); LabelTarget finallyReturn = _returnLabels.Pop(); int finallyYields = _yields.Count; _inTryWithFinally = savedInTryWithFinally; if (@try == node.Body && handlers == node.Handlers && @finally == node.Finally && fault == node.Fault) { return(node); } // No yields, just return if (startYields == _yields.Count) { return(Expression.MakeTry(@try, @finally, fault, handlers)); } if (fault != null && finallyYields != catchYields) { // No one needs this yet, and it's not clear how we should get back to // the fault throw new NotSupportedException("yield in fault block is not supported"); } // If try has yields, we need to build a new try body that // dispatches to the yield labels var tryStart = Expression.Label(); if (tryYields != startYields) { @try = Expression.Block(MakeYieldRouter(startYields, tryYields, tryStart), @try); } // Transform catches with yield to deferred handlers if (catchYields != tryYields) { // Temps which are only needed temporarily, so they can go into // a transient scope (contents will be lost each yield) var temps = new List <ParameterExpression>(); var block = new List <Expression>(); block.Add(MakeYieldRouter(tryYields, catchYields, tryStart)); block.Add(null); // empty slot to fill in later for (int i = 0, n = handlers.Count; i < n; i++) { CatchBlock c = handlers[i]; if (c == node.Handlers[i]) { continue; } if (handlers.IsReadOnly) { handlers = handlers.ToArray(); } // TODO: when CatchBlock's variable is scoped properly, this // implementation will need to be different var deferredVar = Expression.Variable(c.Test, null); temps.Add(deferredVar); handlers[i] = Expression.Catch(deferredVar, Expression.Empty(), c.Filter); // We need to rewrite rethrows into "throw deferredVar" var catchBody = new RethrowRewriter { Exception = deferredVar }.Visit(c.Body); if (c.Variable != null) { catchBody = Expression.Block(Expression.Assign(c.Variable, deferredVar), catchBody); } else { catchBody = Expression.Block(catchBody); } block.Add( Expression.Condition( Expression.NotEqual(deferredVar, Expression.Constant(null, deferredVar.Type)), Expression.Void(catchBody), Expression.Empty() ) ); } block[1] = Expression.MakeTry(@try, null, null, new ReadOnlyCollection <CatchBlock>(handlers)); @try = Expression.Block(temps, block); handlers = new CatchBlock[0]; // so we don't reuse these } if (finallyYields != catchYields) { // We need to add a catch block to save the exception, so we // can rethrow in case there is a yield in the finally. Also, // add logic for returning. It looks like this: // try { ... } catch (Exception e) {} // finally { // if (_finallyReturnVar) goto finallyReturn; // ... // if (e != null) throw e; // finallyReturn: // } // if (_finallyReturnVar) goto _return; // We need to add a catch(Exception), so if we have catches, // wrap them in a try if (handlers.Count > 0) { @try = Expression.MakeTry(@try, null, null, handlers); handlers = new CatchBlock[0]; } // NOTE: the order of these routers is important // The first call changes the labels to all point at "tryEnd", // so the second router will jump to "tryEnd" var tryEnd = Expression.Label(); Expression inFinallyRouter = MakeYieldRouter(catchYields, finallyYields, tryEnd); Expression inTryRouter = MakeYieldRouter(catchYields, finallyYields, tryStart); var exception = Expression.Variable(typeof(Exception), "$temp$" + _temps.Count); _temps.Add(exception); @try = Expression.Block( Expression.TryCatchFinally( Expression.Block( inTryRouter, @try, Expression.Assign(exception, Expression.Constant(null, exception.Type)), Expression.Label(tryEnd) ), Expression.Block( MakeSkipFinallyBlock(finallyReturn), inFinallyRouter, @finally, Expression.Condition( Expression.NotEqual(exception, Expression.Constant(null, exception.Type)), Expression.Throw(exception), Expression.Empty() ), Expression.Label(finallyReturn) ), Expression.Catch(exception, Expression.Empty()) ), Expression.Condition( Expression.Equal(_gotoRouter, Expression.Constant(GotoRouterYielding)), Expression.Goto(_returnLabels.Peek()), Expression.Empty() ) ); @finally = null; } else if (@finally != null) { // try or catch had a yield, modify finally so we can skip over it @finally = Expression.Block( MakeSkipFinallyBlock(finallyReturn), @finally, Expression.Label(finallyReturn) ); } // Make the outer try, if needed if (handlers.Count > 0 || @finally != null || fault != null) { @try = Expression.MakeTry(@try, @finally, fault, handlers); } return(Expression.Block(Expression.Label(tryStart), @try)); }