|
/* ****************************************************************************
*
* Copyright (c) Microsoft Corporation.
*
* This source code is subject to terms and conditions of the Apache License, Version 2.0. A
* copy of the license can be found in the License.html file at the root of this distribution. If
* you cannot locate the Apache License, Version 2.0, please send an email to
* dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
* by the terms of the Apache License, Version 2.0.
*
* You must not remove this notice, or any other, from this software.
*
*
* ***************************************************************************/
using System.Diagnostics;
using System.Dynamic.Utils;
#if CLR2
namespace Microsoft.Scripting.Ast.Compiler {
#else
namespace System.Linq.Expressions.Compiler {
#endif
// The part of the LambdaCompiler dealing with low level control flow
// break, contiue, return, exceptions, etc
partial class LambdaCompiler {
private LabelInfo EnsureLabel(LabelTarget node) {
LabelInfo result;
if (!_labelInfo.TryGetValue(node, out result)) {
_labelInfo.Add(node, result = new LabelInfo(_ilg, node, false));
}
return result;
}
private LabelInfo ReferenceLabel(LabelTarget node) {
LabelInfo result = EnsureLabel(node);
result.Reference(_labelBlock);
return result;
}
private LabelInfo DefineLabel(LabelTarget node) {
if (node == null) {
return new LabelInfo(_ilg, null, false);
}
LabelInfo result = EnsureLabel(node);
result.Define(_labelBlock);
return result;
}
private void PushLabelBlock(LabelScopeKind type) {
_labelBlock = new LabelScopeInfo(_labelBlock, type);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "kind")]
private void PopLabelBlock(LabelScopeKind kind) {
Debug.Assert(_labelBlock != null && _labelBlock.Kind == kind);
_labelBlock = _labelBlock.Parent;
}
private void EmitLabelExpression(Expression expr, CompilationFlags flags) {
var node = (LabelExpression)expr;
Debug.Assert(node.Target != null);
// If we're an immediate child of a block, our label will already
// be defined. If not, we need to define our own block so this
// label isn't exposed except to its own child expression.
LabelInfo label = null;
if (_labelBlock.Kind == LabelScopeKind.Block) {
_labelBlock.TryGetLabelInfo(node.Target, out label);
// We're in a block but didn't find our label, try switch
if (label == null && _labelBlock.Parent.Kind == LabelScopeKind.Switch) {
_labelBlock.Parent.TryGetLabelInfo(node.Target, out label);
}
// if we're in a switch or block, we should've found the label
Debug.Assert(label != null);
}
if (label == null) {
label = DefineLabel(node.Target);
}
if (node.DefaultValue != null) {
if (node.Target.Type == typeof(void)) {
EmitExpressionAsVoid(node.DefaultValue, flags);
} else {
flags = UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitExpressionStart);
EmitExpression(node.DefaultValue, flags);
}
}
label.Mark();
}
private void EmitGotoExpression(Expression expr, CompilationFlags flags) {
var node = (GotoExpression)expr;
var labelInfo = ReferenceLabel(node.Target);
var tailCall = flags & CompilationFlags.EmitAsTailCallMask;
if (tailCall != CompilationFlags.EmitAsNoTail) {
// Since tail call flags are not passed into EmitTryExpression, CanReturn
// means the goto will be emitted as Ret. Therefore we can emit the goto's
// default value with tail call. This can be improved by detecting if the
// target label is equivalent to the return label.
tailCall = labelInfo.CanReturn ? CompilationFlags.EmitAsTail : CompilationFlags.EmitAsNoTail;
flags = UpdateEmitAsTailCallFlag(flags, tailCall);
}
if (node.Value != null) {
if (node.Target.Type == typeof(void)) {
EmitExpressionAsVoid(node.Value, flags);
} else {
flags = UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitExpressionStart);
EmitExpression(node.Value, flags);
}
}
labelInfo.EmitJump();
EmitUnreachable(node, flags);
}
// We need to push default(T), unless we're emitting ourselves as
// void. Even though the code is unreachable, we still have to
// generate correct IL. We can get rid of this once we have better
// reachability analysis.
private void EmitUnreachable(Expression node, CompilationFlags flags) {
if (node.Type != typeof(void) && (flags & CompilationFlags.EmitAsVoidType) == 0) {
_ilg.EmitDefault(node.Type);
}
}
private bool TryPushLabelBlock(Expression node) {
// Anything that is "statement-like" -- e.g. has no associated
// stack state can be jumped into, with the exception of try-blocks
// We indicate this by a "Block"
//
// Otherwise, we push an "Expression" to indicate that it can't be
// jumped into
switch (node.NodeType) {
default:
if (_labelBlock.Kind != LabelScopeKind.Expression) {
PushLabelBlock(LabelScopeKind.Expression);
return true;
}
return false;
case ExpressionType.Label:
// LabelExpression is a bit special, if it's directly in a
// block it becomes associate with the block's scope. Same
// thing if it's in a switch case body.
if (_labelBlock.Kind == LabelScopeKind.Block) {
var label = ((LabelExpression)node).Target;
if (_labelBlock.ContainsTarget(label)) {
return false;
}
if (_labelBlock.Parent.Kind == LabelScopeKind.Switch &&
_labelBlock.Parent.ContainsTarget(label)) {
return false;
}
}
PushLabelBlock(LabelScopeKind.Statement);
return true;
case ExpressionType.Block:
if (node is SpilledExpressionBlock) {
// treat it as an expression
goto default;
}
PushLabelBlock(LabelScopeKind.Block);
// Labels defined immediately in the block are valid for
// the whole block.
if (_labelBlock.Parent.Kind != LabelScopeKind.Switch) {
DefineBlockLabels(node);
}
return true;
case ExpressionType.Switch:
PushLabelBlock(LabelScopeKind.Switch);
// Define labels inside of the switch cases so theyare in
// scope for the whole switch. This allows "goto case" and
// "goto default" to be considered as local jumps.
var @switch = (SwitchExpression)node;
foreach (SwitchCase c in @switch.Cases) {
DefineBlockLabels(c.Body);
}
DefineBlockLabels(@switch.DefaultBody);
return true;
// Remove this when Convert(Void) goes away.
case ExpressionType.Convert:
if (node.Type != typeof(void)) {
// treat it as an expression
goto default;
}
PushLabelBlock(LabelScopeKind.Statement);
return true;
case ExpressionType.Conditional:
case ExpressionType.Loop:
case ExpressionType.Goto:
PushLabelBlock(LabelScopeKind.Statement);
return true;
}
}
private void DefineBlockLabels(Expression node) {
var block = node as BlockExpression;
if (block == null || block is SpilledExpressionBlock) {
return;
}
for (int i = 0, n = block.ExpressionCount; i < n; i++) {
Expression e = block.GetExpression(i);
var label = e as LabelExpression;
if (label != null) {
DefineLabel(label.Target);
}
}
}
// See if this lambda has a return label
// If so, we'll create it now and mark it as allowing the "ret" opcode
// This allows us to generate better IL
private void AddReturnLabel(LambdaExpression lambda) {
var expression = lambda.Body;
while (true) {
switch (expression.NodeType) {
default:
// Didn't find return label
return;
case ExpressionType.Label:
// Found the label. We can directly return from this place
// only if the label type is reference assignable to the lambda return type.
var label = ((LabelExpression)expression).Target;
_labelInfo.Add(label, new LabelInfo(_ilg, label, TypeUtils.AreReferenceAssignable(lambda.ReturnType, label.Type)));
return;
case ExpressionType.Block:
// Look in the last significant expression of a block
var body = (BlockExpression)expression;
// omit empty and debuginfo at the end of the block since they
// are not going to emit any IL
for (int i = body.ExpressionCount - 1; i >= 0; i--) {
expression = body.GetExpression(i);
if (Significant(expression)) {
break;
}
}
continue;
}
}
}
}
}
|