/// <summary> /// This method emits code for a lookup-based switch statement (non-string) /// Basically it groups the cases into blocks that are at least half full, /// and then spits out individual lookup opcodes for each block. /// It emits the longest blocks first, and short blocks are just /// handled with direct compares. /// </summary> /// <param name="ec"></param> /// <param name="val"></param> /// <returns></returns> void TableSwitchEmit (EmitContext ec, Expression val) { int element_count = Elements.Count; object [] element_keys = new object [element_count]; Elements.Keys.CopyTo (element_keys, 0); Array.Sort (element_keys); // initialize the block list with one element per key var key_blocks = new List<KeyBlock> (element_count); foreach (object key in element_keys) key_blocks.Add (new KeyBlock (System.Convert.ToInt64 (key))); KeyBlock current_kb; // iteratively merge the blocks while they are at least half full // there's probably a really cool way to do this with a tree... while (key_blocks.Count > 1) { var key_blocks_new = new List<KeyBlock> (); current_kb = (KeyBlock) key_blocks [0]; for (int ikb = 1; ikb < key_blocks.Count; ikb++) { KeyBlock kb = (KeyBlock) key_blocks [ikb]; if ((current_kb.Size + kb.Size) * 2 >= KeyBlock.TotalLength (current_kb, kb)) { // merge blocks current_kb.last = kb.last; current_kb.Size += kb.Size; } else { // start a new block key_blocks_new.Add (current_kb); current_kb = kb; } } key_blocks_new.Add (current_kb); if (key_blocks.Count == key_blocks_new.Count) break; key_blocks = key_blocks_new; } // initialize the key lists foreach (KeyBlock kb in key_blocks) kb.element_keys = new List<object> (); // fill the key lists int iBlockCurr = 0; if (key_blocks.Count > 0) { current_kb = (KeyBlock) key_blocks [0]; foreach (object key in element_keys) { bool next_block = (key is UInt64) ? (ulong) key > (ulong) current_kb.last : System.Convert.ToInt64 (key) > current_kb.last; if (next_block) current_kb = (KeyBlock) key_blocks [++iBlockCurr]; current_kb.element_keys.Add (key); } } // sort the blocks so we can tackle the largest ones first key_blocks.Sort (); // okay now we can start... Label lbl_end = ec.DefineLabel (); // at the end ;-) Label lbl_default = default_target; Type type_keys = null; if (element_keys.Length > 0) type_keys = element_keys [0].GetType (); // used for conversions TypeSpec compare_type; if (TypeManager.IsEnumType (SwitchType)) compare_type = EnumSpec.GetUnderlyingType (SwitchType); else compare_type = SwitchType; for (int iBlock = key_blocks.Count - 1; iBlock >= 0; --iBlock) { KeyBlock kb = ((KeyBlock) key_blocks [iBlock]); lbl_default = (iBlock == 0) ? default_target : ec.DefineLabel (); if (kb.Length <= 2) { foreach (object key in kb.element_keys) { SwitchLabel sl = (SwitchLabel) Elements [key]; if (key is int && (int) key == 0) { val.EmitBranchable (ec, sl.GetILLabel (ec), false); } else { val.Emit (ec); EmitObjectInteger (ec, key); ec.Emit (OpCodes.Beq, sl.GetILLabel (ec)); } } } else { // TODO: if all the keys in the block are the same and there are // no gaps/defaults then just use a range-check. if (compare_type == TypeManager.int64_type || compare_type == TypeManager.uint64_type) { // TODO: optimize constant/I4 cases // check block range (could be > 2^31) val.Emit (ec); EmitObjectInteger (ec, System.Convert.ChangeType (kb.first, type_keys)); ec.Emit (OpCodes.Blt, lbl_default); val.Emit (ec); EmitObjectInteger (ec, System.Convert.ChangeType (kb.last, type_keys)); ec.Emit (OpCodes.Bgt, lbl_default); // normalize range val.Emit (ec); if (kb.first != 0) { EmitObjectInteger (ec, System.Convert.ChangeType (kb.first, type_keys)); ec.Emit (OpCodes.Sub); } ec.Emit (OpCodes.Conv_I4); // assumes < 2^31 labels! } else { // normalize range val.Emit (ec); int first = (int) kb.first; if (first > 0) { ec.EmitInt (first); ec.Emit (OpCodes.Sub); } else if (first < 0) { ec.EmitInt (-first); ec.Emit (OpCodes.Add); } } // first, build the list of labels for the switch int iKey = 0; int cJumps = kb.Length; Label [] switch_labels = new Label [cJumps]; for (int iJump = 0; iJump < cJumps; iJump++) { object key = kb.element_keys [iKey]; if (System.Convert.ToInt64 (key) == kb.first + iJump) { SwitchLabel sl = (SwitchLabel) Elements [key]; switch_labels [iJump] = sl.GetILLabel (ec); iKey++; } else switch_labels [iJump] = lbl_default; } // emit the switch opcode ec.Emit (OpCodes.Switch, switch_labels); } // mark the default for this block if (iBlock != 0) ec.MarkLabel (lbl_default); } // TODO: find the default case and emit it here, // to prevent having to do the following jump. // make sure to mark other labels in the default section // the last default just goes to the end if (element_keys.Length > 0) ec.Emit (OpCodes.Br, lbl_default); // now emit the code for the sections bool found_default = false; foreach (SwitchSection ss in Sections) { foreach (SwitchLabel sl in ss.Labels) { if (sl.Converted == SwitchLabel.NullStringCase) { ec.MarkLabel (null_target); } else if (sl.Label == null) { ec.MarkLabel (lbl_default); found_default = true; if (!has_null_case) ec.MarkLabel (null_target); } ec.MarkLabel (sl.GetILLabel (ec)); ec.MarkLabel (sl.GetILLabelCode (ec)); } ss.Block.Emit (ec); } if (!found_default) { ec.MarkLabel (lbl_default); if (!has_null_case) { ec.MarkLabel (null_target); } } ec.MarkLabel (lbl_end); }