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);
}