Files
Flee/ExpressionElements/LogicalBitwise/AndOr.cs

350 lines
12 KiB
C#

using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection.Emit;
using Flee.ExpressionElements.Base;
using Flee.InternalTypes;
namespace Flee.ExpressionElements.LogicalBitwise
{
internal class AndOrElement : BinaryExpressionElement
{
private AndOrOperation _myOperation;
private static readonly object OurTrueTerminalKey = new object();
private static readonly object OurFalseTerminalKey = new object();
private static readonly object OurEndLabelKey = new object();
public void New()
{
}
protected override void GetOperation(object operation)
{
_myOperation = (AndOrOperation)operation;
}
protected override System.Type GetResultType(System.Type leftType, System.Type rightType)
{
Type bitwiseOpType = Utility.GetBitwiseOpType(leftType, rightType);
if ((bitwiseOpType != null))
{
return bitwiseOpType;
}
else if (this.AreBothChildrenOfType(typeof(bool)))
{
return typeof(bool);
}
else
{
return null;
}
}
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
{
Type resultType = this.ResultType;
if (object.ReferenceEquals(resultType, typeof(bool)))
{
this.DoEmitLogical(ilg, services);
}
else
{
MyLeftChild.Emit(ilg, services);
ImplicitConverter.EmitImplicitConvert(MyLeftChild.ResultType, resultType, ilg);
MyRightChild.Emit(ilg, services);
ImplicitConverter.EmitImplicitConvert(MyRightChild.ResultType, resultType, ilg);
EmitBitwiseOperation(ilg, _myOperation);
}
}
private static void EmitBitwiseOperation(FleeILGenerator ilg, AndOrOperation op)
{
switch (op)
{
case AndOrOperation.And:
ilg.Emit(OpCodes.And);
break;
case AndOrOperation.Or:
ilg.Emit(OpCodes.Or);
break;
default:
Debug.Fail("Unknown op type");
break;
}
}
private void DoEmitLogical(FleeILGenerator ilg, IServiceProvider services)
{
// We have to do a 'fake' emit so we can get the positions of the labels
ShortCircuitInfo info = new ShortCircuitInfo();
// Do the real emit
this.EmitLogical(ilg, info, services);
}
/// <summary>
/// Emit a short-circuited logical operation sequence
/// The idea: Store all the leaf operands in a stack with the leftmost at the top and rightmost at the bottom.
/// For each operand, emit it and try to find an end point for when it short-circuits. This means we go up through
/// the stack of operators (ignoring siblings) until we find a different operation (then emit a branch to its right operand)
/// or we reach the root (emit a branch to a true/false).
/// Repeat the process for all operands and then emit the true/false/last operand end cases.
/// </summary>
/// <param name="ilg"></param>
/// <param name="info"></param>
/// <param name="services"></param>
private void EmitLogical(FleeILGenerator ilg, ShortCircuitInfo info, IServiceProvider services)
{
// We always have an end label
Label endLabel = ilg.DefineLabel();
// Populate our data structures
this.PopulateData(info);
// Emit the sequence
EmitLogicalShortCircuit(ilg, info, services);
// Get the last operand
ExpressionElement terminalOperand = (ExpressionElement)info.Operands.Pop();
// Emit it
EmitOperand(terminalOperand, info, ilg, services);
// only 1-3 opcodes, always a short branch
ilg.EmitBranch(endLabel);
// Emit our true/false terminals
EmitTerminals(info, ilg, endLabel);
// Mark the end
ilg.MarkLabel(endLabel);
}
/// <summary>
/// Emit a sequence of and/or expressions with short-circuiting
/// </summary>
/// <param name="ilg"></param>
/// <param name="info"></param>
/// <param name="services"></param>
private static void EmitLogicalShortCircuit(FleeILGenerator ilg, ShortCircuitInfo info, IServiceProvider services)
{
while (info.Operators.Count != 0)
{
// Get the operator
AndOrElement op = (AndOrElement)info.Operators.Pop();
// Get the left operand
ExpressionElement leftOperand = (ExpressionElement)info.Operands.Pop();
// Emit the left
EmitOperand(leftOperand, info, ilg, services);
// Get the label for the short-circuit case
Label l = GetShortCircuitLabel(op, info, ilg);
// Emit the branch
EmitBranch(op, ilg, l, info);
}
}
private static void EmitBranch(AndOrElement op, FleeILGenerator ilg, Label target, ShortCircuitInfo info)
{
// Get the branch opcode
if (op._myOperation == AndOrOperation.And)
ilg.EmitBranchFalse(target);
else
ilg.EmitBranchTrue(target);
}
/// <summary>
/// Get the label for a short-circuit
/// </summary>
/// <param name="current"></param>
/// <param name="info"></param>
/// <param name="ilg"></param>
/// <returns></returns>
private static Label GetShortCircuitLabel(AndOrElement current, ShortCircuitInfo info, FleeILGenerator ilg)
{
// We modify the given stacks so we need to clone them
Stack cloneOperands = (Stack)info.Operands.Clone();
Stack cloneOperators = (Stack)info.Operators.Clone();
// Pop all siblings
current.PopRightChild(cloneOperands, cloneOperators);
// Go until we run out of operators
while (cloneOperators.Count > 0)
{
// Get the top operator
AndOrElement top = (AndOrElement)cloneOperators.Pop();
// Is is a different operation?
if (top._myOperation != current._myOperation)
{
// Yes, so return a label to its right operand
object nextOperand = cloneOperands.Pop();
return GetLabel(nextOperand, ilg, info);
}
else
{
// No, so keep going up the stack
top.PopRightChild(cloneOperands, cloneOperators);
}
}
// We've reached the end of the stack so return the label for the appropriate true/false terminal
if (current._myOperation == AndOrOperation.And)
{
return GetLabel(OurFalseTerminalKey, ilg, info);
}
else
{
return GetLabel(OurTrueTerminalKey, ilg, info);
}
}
private void PopRightChild(Stack operands, Stack operators)
{
AndOrElement andOrChild = MyRightChild as AndOrElement;
// What kind of child do we have?
if ((andOrChild != null))
{
// Another and/or expression so recurse
andOrChild.Pop(operands, operators);
}
else
{
// A terminal so pop it off the operands stack
operands.Pop();
}
}
/// <summary>
/// Recursively pop operators and operands
/// </summary>
/// <param name="operands"></param>
/// <param name="operators"></param>
private void Pop(Stack operands, Stack operators)
{
operators.Pop();
AndOrElement andOrChild = MyLeftChild as AndOrElement;
if (andOrChild == null)
{
operands.Pop();
}
else
{
andOrChild.Pop(operands, operators);
}
andOrChild = MyRightChild as AndOrElement;
if (andOrChild == null)
{
operands.Pop();
}
else
{
andOrChild.Pop(operands, operators);
}
}
private static void EmitOperand(ExpressionElement operand, ShortCircuitInfo info, FleeILGenerator ilg, IServiceProvider services)
{
// Is this operand the target of a label?
if (info.HasLabel(operand) == true)
{
// Yes, so mark it
Label leftLabel = info.FindLabel(operand);
ilg.MarkLabel(leftLabel);
}
// Emit the operand
operand.Emit(ilg, services);
}
/// <summary>
/// Emit the end cases for a short-circuit
/// </summary>
/// <param name="info"></param>
/// <param name="ilg"></param>
/// <param name="endLabel"></param>
private static void EmitTerminals(ShortCircuitInfo info, FleeILGenerator ilg, Label endLabel)
{
// Emit the false case if it was used
if (info.HasLabel(OurFalseTerminalKey) == true)
{
Label falseLabel = info.FindLabel(OurFalseTerminalKey);
// Mark the label and note its position
ilg.MarkLabel(falseLabel);
ilg.Emit(OpCodes.Ldc_I4_0);
// If we also have a true terminal, then skip over it
if (info.HasLabel(OurTrueTerminalKey) == true)
{
// only 1-3 opcodes, always a short branch
ilg.Emit(OpCodes.Br_S, endLabel);
}
}
// Emit the true case if it was used
if (info.HasLabel(OurTrueTerminalKey) == true)
{
Label trueLabel = info.FindLabel(OurTrueTerminalKey);
// Mark the label and note its position
ilg.MarkLabel(trueLabel);
ilg.Emit(OpCodes.Ldc_I4_1);
}
}
private static Label GetLabel(object key, FleeILGenerator ilg, ShortCircuitInfo info)
{
if (info.HasLabel(key))
return info.FindLabel(key);
return info.AddLabel(key, ilg.DefineLabel());
}
/// <summary>
/// Visit the nodes of the tree (right then left) and populate some data structures
/// </summary>
/// <param name="info"></param>
private void PopulateData(ShortCircuitInfo info)
{
// Is our right child a leaf or another And/Or expression?
AndOrElement andOrChild = MyRightChild as AndOrElement;
if (andOrChild == null)
{
// Leaf so push it on the stack
info.Operands.Push(MyRightChild);
}
else
{
// Another And/Or expression so recurse
andOrChild.PopulateData(info);
}
// Add ourselves as an operator
info.Operators.Push(this);
// Do the same thing for the left child
andOrChild = MyLeftChild as AndOrElement;
if (andOrChild == null)
{
info.Operands.Push(MyLeftChild);
}
else
{
andOrChild.PopulateData(info);
}
}
}
}