Generate better code for small switch statements
[mono.git] / mcs / mcs / statement.cs
index 77f836ce565720f554e6fdf734ea39dccf598bcf..7b68fa9e5d345b39fbfaf331a591468c2bf1a357 100644 (file)
@@ -1,10 +1,10 @@
 //
 // statement.cs: Statement representation for the IL tree.
 //
-// Author:
+// Authors:
 //   Miguel de Icaza (miguel@ximian.com)
 //   Martin Baulig (martin@ximian.com)
-//   Marek Safar (marek.safar@seznam.cz)
+//   Marek Safar (marek.safar@gmail.com)
 //
 // Copyright 2001, 2002, 2003 Ximian, Inc.
 // Copyright 2003, 2004 Novell, Inc.
@@ -1807,7 +1807,8 @@ namespace Mono.CSharp {
                        HasCapturedThis = 1 << 7,
                        IsExpressionTree = 1 << 8,
                        CompilerGenerated = 1 << 9,
-                       IsAsync = 1 << 10
+                       IsAsync = 1 << 10,
+                       Resolved = 1 << 11
                }
 
                public Block Parent;
@@ -1996,6 +1997,9 @@ namespace Mono.CSharp {
 
                public override bool Resolve (BlockContext ec)
                {
+                       if ((flags & Flags.Resolved) != 0)
+                               return true;
+
                        Block prev_block = ec.CurrentBlock;
                        bool ok = true;
 
@@ -2080,6 +2084,7 @@ namespace Mono.CSharp {
                        if (this == ParametersBlock.TopBlock && !ParametersBlock.TopBlock.IsThisAssigned (ec) && !flow_unreachable)
                                ok = false;
 
+                       flags |= Flags.Resolved;
                        return ok;
                }
 
@@ -3258,6 +3263,32 @@ namespace Mono.CSharp {
                        }
                }
 
+               sealed class LabelMarker : Statement
+               {
+                       readonly Switch s;
+                       readonly List<SwitchLabel> labels;
+
+                       public LabelMarker (Switch s, List<SwitchLabel> labels)
+                       {
+                               this.s = s;
+                               this.labels = labels;
+                       }
+
+                       protected override void CloneTo (CloneContext clonectx, Statement target)
+                       {
+                       }
+
+                       protected override void DoEmit (EmitContext ec)
+                       {
+                               foreach (var l in labels) {
+                                       if (l.IsDefault)
+                                               ec.MarkLabel (s.DefaultLabel);
+                                       else
+                                               ec.MarkLabel (l.GetILLabel (ec));
+                               }
+                       }
+               }
+
                public List<SwitchSection> Sections;
                public Expression Expr;
 
@@ -3284,6 +3315,8 @@ namespace Mono.CSharp {
                SwitchSection default_section;
                SwitchLabel null_section;
 
+               Statement simple_stmt;
+               VariableReference value;
                ExpressionStatement string_dictionary;
                FieldExpr switch_cache_field;
                static int unique_counter;
@@ -3701,6 +3734,13 @@ namespace Mono.CSharp {
 
                                if (constant_section == null)
                                        constant_section = default_section;
+                       } else {
+                               //
+                               // Store switch expression for comparission purposes
+                               //
+                               value = new_expr as VariableReference;
+                               if (value == null)
+                                       value = TemporaryVariableReference.Create (SwitchType, ec.CurrentBlock, loc);
                        }
 
                        bool first = true;
@@ -3727,8 +3767,7 @@ namespace Mono.CSharp {
                        }
 
                        if (default_section == null)
-                               ec.CurrentBranching.CreateSibling (
-                                       null, FlowBranching.SiblingType.SwitchSection);
+                               ec.CurrentBranching.CreateSibling (null, FlowBranching.SiblingType.SwitchSection);
 
                        ec.EndFlowBranching ();
                        ec.Switch = old_switch;
@@ -3736,9 +3775,15 @@ namespace Mono.CSharp {
                        if (!ok)
                                return false;
 
-                       if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.String && !is_constant) {
-                               // TODO: Optimize single case, and single+default case
-                               ResolveStringSwitchMap (ec);
+                       if (!is_constant) {
+                               if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.String) {
+                                       if (string_labels.Count < 7)
+                                               ResolveSimpleSwitch (ec);
+                                       else
+                                               ResolveStringSwitchMap (ec);
+                               } else if (labels.Count < 3 && !IsNullable) {
+                                       ResolveSimpleSwitch (ec);
+                               }
                        }
 
                        return true;
@@ -3755,6 +3800,45 @@ namespace Mono.CSharp {
                        return sl;
                }
 
+               //
+               // Prepares switch using simple if/else comparison for small label count (4 + optional default)
+               //
+               void ResolveSimpleSwitch (BlockContext bc)
+               {
+                       simple_stmt = default_section != null ? default_section.Block : null;
+
+                       for (int i = Sections.Count - 1; i >= 0; --i) {
+                               var s = Sections[i];
+
+                               if (s == default_section) {
+                                       s.Block.AddScopeStatement (new LabelMarker (this, s.Labels));
+                                       continue;
+                               }
+
+                               s.Block.AddScopeStatement (new LabelMarker (this, s.Labels));
+
+                               Expression cond = null;
+                               for (int ci = 0; ci < s.Labels.Count; ++ci) {
+                                       var e = new Binary (Binary.Operator.Equality, value, s.Labels[ci].Converted, loc);
+
+                                       if (ci > 0) {
+                                               cond = new Binary (Binary.Operator.LogicalOr, cond, e, loc);
+                                       } else {
+                                               cond = e;
+                                       }
+                               }
+
+                               simple_stmt = new If (cond, s.Block, simple_stmt, loc);
+                       }
+
+                       // It's null for empty switch
+                       if (simple_stmt != null)
+                               simple_stmt.Resolve (bc);
+               }
+
+               //
+               // Converts string switch into string hashtable
+               //
                void ResolveStringSwitchMap (ResolveContext ec)
                {
                        FullNamedExpression string_dictionary_type;
@@ -3819,7 +3903,7 @@ namespace Mono.CSharp {
                        string_dictionary = new SimpleAssign (switch_cache_field, initializer.Resolve (ec));
                }
 
-               void DoEmitStringSwitch (LocalTemporary value, EmitContext ec)
+               void DoEmitStringSwitch (EmitContext ec)
                {
                        Label l_initialized = ec.DefineLabel ();
 
@@ -3873,7 +3957,7 @@ namespace Mono.CSharp {
                        EmitTableSwitch (ec, string_switch_variable);
                        string_switch_variable.Release (ec);
                }
-               
+
                protected override void DoEmit (EmitContext ec)
                {
                        //
@@ -3885,21 +3969,13 @@ namespace Mono.CSharp {
                        default_target = ec.DefineLabel ();
                        null_target = ec.DefineLabel ();
 
-                       // Store variable for comparission purposes
-                       // TODO: Don't duplicate non-captured VariableReference
-                       LocalTemporary value;
                        if (IsNullable) {
-                               value = new LocalTemporary (SwitchType);
                                unwrap.EmitCheck (ec);
                                ec.Emit (OpCodes.Brfalse, null_target);
-                               new_expr.Emit (ec);
-                               value.Store (ec);
-                       } else if (!is_constant) {
-                               value = new LocalTemporary (SwitchType);
-                               new_expr.Emit (ec);
-                               value.Store (ec);
-                       } else
-                               value = null;
+                               value.EmitAssign (ec, new_expr, false, false);
+                       } else if (new_expr != value && !is_constant) {
+                               value.EmitAssign (ec, new_expr, false, false);
+                       }
 
                        //
                        // Setup the codegen context
@@ -3915,14 +3991,13 @@ namespace Mono.CSharp {
                                if (constant_section != null)
                                        constant_section.Block.Emit (ec);
                        } else if (string_dictionary != null) {
-                               DoEmitStringSwitch (value, ec);
+                               DoEmitStringSwitch (ec);
+                       } else if (simple_stmt != null) {
+                               simple_stmt.Emit (ec);
                        } else {
                                EmitTableSwitch (ec, value);
                        }
 
-                       if (value != null)
-                               value.Release (ec);
-
                        // Restore context state. 
                        ec.MarkLabel (ec.LoopEnd);