protected virtual void _Generate(AstNodeStmSwitch Switch) { Output.Write("switch ("); Generate(Switch.SwitchValue); Output.Write(") {").WriteNewLine(); Output.Indent(() => { foreach (var Case in Switch.Cases) { Generate(Case); } if (Switch.CaseDefault != null) { Generate(Switch.CaseDefault); } }); Output.Write("}"); }
protected virtual void _Generate(AstNodeStmSwitch Switch) { var allCaseValues = Switch.Cases.Select(Case => Case.CaseValue); var caseValues = allCaseValues as IList <object> ?? allCaseValues.ToList(); if (caseValues.Count != caseValues.Distinct().Count()) { throw new Exception("Repeated case in switch!"); } // Check types and unique values. var endCasesLabel = AstLabel.CreateLabel("EndCasesLabel"); var defaultLabel = AstLabel.CreateLabel("DefaultLabel"); if (Switch.Cases.Length > 0) { var commonType = Switch.Cases.First().CaseValue.GetType(); if (Switch.Cases.Any(Case => Case.CaseValue.GetType() != commonType)) { throw new Exception("All cases should have the same type"); } var doneSpecialized = false; // Specialized constant-time integer switch (if possible) if (AstUtils.IsIntegerType(commonType)) { var commonMin = Switch.Cases.Min(Case => AstUtils.CastType <long>(Case.CaseValue)); var commonMax = Switch.Cases.Max(Case => AstUtils.CastType <long>(Case.CaseValue)); var casesLength = commonMax - commonMin + 1; // No processing tables greater than 4096 elements. if (casesLength <= 4096) { var labels = new AstLabel[casesLength]; for (var n = 0; n < casesLength; n++) { labels[n] = defaultLabel; } foreach (var Case in Switch.Cases) { var realValue = AstUtils.CastType <long>(Case.CaseValue); var offset = realValue - commonMin; labels[offset] = AstLabel.CreateLabel("Case_" + realValue); } /* * //var SwitchVarLocal = AstLocal.Create(AllCaseValues.First().GetType(), "SwitchVarLocal" + SwitchVarCount++); * //Generate(new AstNodeStmAssign(new AstNodeExprLocal(SwitchVarLocal), Switch.SwitchValue - new AstNodeExprCast(CommonType, CommonMin))); * //Generate(new AstNodeStmIfElse(new AstNodeExprBinop(new AstNodeExprLocal(SwitchVarLocal), "<", 0), new AstNodeStmGotoAlways(DefaultLabel))); * //Generate(new AstNodeStmIfElse(new AstNodeExprBinop(new AstNodeExprLocal(SwitchVarLocal), ">=", CasesLength), new AstNodeStmGotoAlways(DefaultLabel))); * //Generate(new AstNodeExprLocal(SwitchVarLocal)); */ Generate(Switch.SwitchValue - new AstNodeExprCast(commonType, commonMin)); Emit(OpCodes.Switch, labels); Generate(new AstNodeStmGotoAlways(defaultLabel)); foreach (var Case in Switch.Cases) { var realValue = AstUtils.CastType <long>(Case.CaseValue); var offset = realValue - commonMin; Generate(new AstNodeStmLabel(labels[offset])); { Generate(Case.Code); } Generate(new AstNodeStmGotoAlways(endCasesLabel)); } doneSpecialized = true; } else { // TODO: find a common shift and masks for all the values to reduce CasesLength. // TODO: On too large test cases, split them recursively in: // if (Var < Half) { switch(Var - LowerPartMin) { ... } } else { switch(Var - Half - UpperPartMin) { ... } } } } // Specialized switch for strings (checking length, then hash, then contents) else if (commonType == typeof(string)) { // TODO! } // Generic if/else if (!doneSpecialized) { var switchVarLocal = AstLocal.Create(caseValues.First().GetType(), "SwitchVarLocal" + _switchVarCount++); Generate(new AstNodeStmAssign(new AstNodeExprLocal(switchVarLocal), Switch.SwitchValue)); //Switch.Cases foreach (var Case in Switch.Cases) { var labelSkipThisCase = AstLabel.CreateLabel("LabelCase" + Case.CaseValue); Generate(new AstNodeStmGotoIfFalse(labelSkipThisCase, new AstNodeExprBinop(new AstNodeExprLocal(switchVarLocal), "==", new AstNodeExprImm(Case.CaseValue)))); Generate(Case.Code); Generate(new AstNodeStmGotoAlways(endCasesLabel)); Generate(new AstNodeStmLabel(labelSkipThisCase)); } } } Generate(new AstNodeStmLabel(defaultLabel)); if (Switch.CaseDefault != null) { Generate(Switch.CaseDefault.Code); } Generate(new AstNodeStmLabel(endCasesLabel)); }