private DecisionTree AddByValue(DecisionTree.ByValue byValue, BoundConstantPattern value, DecisionMaker makeDecision) { Debug.Assert(value.Value.Type.Equals(byValue.Type, TypeCompareKind.IgnoreDynamicAndTupleNames)); if (byValue.Default != null) { return(AddByValue(byValue.Default, value, makeDecision)); } // For error recovery, to avoid "unreachable code" diagnostics when there is a bad case // label, we use the case label itself as the value key. object valueKey = value.ConstantValue?.Value ?? value; DecisionTree valueDecision; if (byValue.ValueAndDecision.TryGetValue(valueKey, out valueDecision)) { valueDecision = Add(valueDecision, makeDecision); } else { valueDecision = makeDecision(byValue.Expression, byValue.Type); byValue.ValueAndDecision.Add(valueKey, valueDecision); } if (byValue.Type.SpecialType == SpecialType.System_Boolean && byValue.ValueAndDecision.Count == 2 && byValue.ValueAndDecision.Values.All(d => d.MatchIsComplete)) { byValue.MatchIsComplete = true; } return(valueDecision); }
private DecisionTree AddByValue(DecisionTree.ByValue byValue, BoundConstantPattern value, DecisionMaker makeDecision) { Debug.Assert(value.Value.Type.Equals(byValue.Type, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); if (byValue.Default != null) { return(AddByValue(byValue.Default, value, makeDecision)); } Debug.Assert(value.ConstantValue != null); object valueKey = value.ConstantValue.Value; DecisionTree valueDecision; if (byValue.ValueAndDecision.TryGetValue(valueKey, out valueDecision)) { valueDecision = Add(valueDecision, makeDecision); } else { valueDecision = makeDecision(byValue.Expression, byValue.Type); byValue.ValueAndDecision.Add(valueKey, valueDecision); } if (byValue.Type.SpecialType == SpecialType.System_Boolean && byValue.ValueAndDecision.Count == 2 && byValue.ValueAndDecision.Values.All(d => d.MatchIsComplete)) { byValue.MatchIsComplete = true; } return(valueDecision); }
private void LowerConstantValueDecision(DecisionTree.ByValue byValue) { var value = byValue.Expression.ConstantValue.Value; Debug.Assert(value != null); // because we are switching on a constant, the decision tree builder does not produce a (nonempty) ByValue Debug.Assert(byValue.ValueAndDecision.Count == 0); LowerDecisionTree(byValue.Expression, byValue.Default); }
private void LowerBooleanSwitch(DecisionTree.ByValue byValue) { switch (byValue.ValueAndDecision.Count) { case 0: { // this should have been handled in the caller. throw ExceptionUtilities.Unreachable; } case 1: { DecisionTree decision; bool onBoolean = byValue.ValueAndDecision.TryGetValue(true, out decision); if (!onBoolean) { byValue.ValueAndDecision.TryGetValue(false, out decision); } Debug.Assert(decision != null); var onOther = _factory.GenerateLabel("on" + !onBoolean); _loweredDecisionTree.Add(_factory.ConditionalGoto(byValue.Expression, onOther, !onBoolean)); LowerDecisionTree(byValue.Expression, decision); // if we fall through here, that means the match was not complete and we invoke the default part _loweredDecisionTree.Add(_factory.Label(onOther)); LowerDecisionTree(byValue.Expression, byValue.Default); break; } case 2: { DecisionTree trueDecision, falseDecision; bool hasTrue = byValue.ValueAndDecision.TryGetValue(true, out trueDecision); bool hasFalse = byValue.ValueAndDecision.TryGetValue(false, out falseDecision); Debug.Assert(hasTrue && hasFalse); var tryAnother = _factory.GenerateLabel("tryAnother"); var onFalse = _factory.GenerateLabel("onFalse"); _loweredDecisionTree.Add(_factory.ConditionalGoto(byValue.Expression, onFalse, false)); LowerDecisionTree(byValue.Expression, trueDecision); _loweredDecisionTree.Add(_factory.Goto(tryAnother)); _loweredDecisionTree.Add(_factory.Label(onFalse)); LowerDecisionTree(byValue.Expression, falseDecision); _loweredDecisionTree.Add(_factory.Label(tryAnother)); // if both true and false (i.e. all values) are fully handled, there should be no default. Debug.Assert(!trueDecision.MatchIsComplete || !falseDecision.MatchIsComplete || byValue.Default == null); LowerDecisionTree(byValue.Expression, byValue.Default); break; } default: throw ExceptionUtilities.UnexpectedValue(byValue.ValueAndDecision.Count); } }
private void LowerConstantValueDecision(DecisionTree.ByValue byValue) { var value = byValue.Expression.ConstantValue.Value; Debug.Assert(value != null); DecisionTree onValue; if (byValue.ValueAndDecision.TryGetValue(value, out onValue)) { LowerDecisionTree(byValue.Expression, onValue); if (onValue.MatchIsComplete) { return; } } LowerDecisionTree(byValue.Expression, byValue.Default); }
private DecisionTree Add(DecisionTree.ByValue byValue, DecisionMaker makeDecision) { DecisionTree result; if (byValue.Default != null) { result = Add(byValue.Default, makeDecision); } else { result = byValue.Default = makeDecision(byValue.Expression, byValue.Type); } if (byValue.Default.MatchIsComplete) { byValue.MatchIsComplete = true; } return(result); }
private void LowerDecisionTree(DecisionTree.ByValue byValue) { if (byValue.Expression.ConstantValue != null) { LowerConstantValueDecision(byValue); return; } if (byValue.ValueAndDecision.Count == 0) { LowerDecisionTree(byValue.Expression, byValue.Default); return; } if (byValue.Type.SpecialType == SpecialType.System_Boolean) { LowerBooleanSwitch(byValue); } else { LowerBasicSwitch(byValue); } }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return(AddByValue(byType.Default, value, makeDecision)); } finally { if (byType.Default.MatchIsComplete) { // This code may be unreachable due to https://github.com/dotnet/roslyn/issues/16878 byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { // This should not occur, as the caller will have invoked AddByNull instead. throw ExceptionUtilities.Unreachable; } if ((object)value.Value.Type == null) { return(null); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { return(null); } continue; case false: case null: continue; } } DecisionTree forType = null; // This new type test should logically be last. However it might be the same type as the one that is already // last. In that case we can produce better code by piggy-backing our new case on to the last decision. // Also, the last one might be a non-overlapping type, in which case we can piggy-back onto the second-last // type test. for (int i = byType.TypeAndDecision.Count - 1; i >= 0; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.Equals(value.Value.Type, TypeCompareKind.IgnoreDynamicAndTupleNames)) { forType = decision; break; } else if (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics) != false) { // because there is overlap, we cannot reuse some earlier entry break; } } // if we did not piggy-back, then create a new decision tree node for the type. if (forType == null) { var type = value.Value.Type; var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatching, Syntax, false, RefKind.None); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair <TypeSymbol, DecisionTree>(value.Value.Type, forType)); } return(AddByValue(forType, value, makeDecision)); }
// For switch statements, we have an option of completely rewriting the switch header // and switch sections into simpler constructs, i.e. we can rewrite the switch header // using bound conditional goto statements and the rewrite the switch sections into // bound labeled statements. // // However, all the logic for emitting the switch jump tables is language agnostic // and includes IL optimizations. Hence we delay the switch jump table generation // till the emit phase. This way we also get additional benefit of sharing this code // between both VB and C# compilers. // // For string switch statements, we need to determine if we are generating a hash // table based jump table or a non hash jump table, i.e. linear string comparisons // with each case label. We use the Dev10 Heuristic to determine this // (see SwitchStringJumpTableEmitter.ShouldGenerateHashTableSwitch() for details). // If we are generating a hash table based jump table, we use a simple // hash function to hash the string constants corresponding to the case labels. // See SwitchStringJumpTableEmitter.ComputeStringHash(). // We need to emit this same function to compute the hash value into the compiler generated // <PrivateImplementationDetails> class. // If we have at least one string switch statement in a module that needs a // hash table based jump table, we generate a single public string hash synthesized method // that is shared across the module. private void LowerBasicSwitch(DecisionTree.ByValue byValue) { var switchSections = ArrayBuilder <BoundSwitchSection> .GetInstance(); var noValueMatches = _factory.GenerateLabel("noValueMatches"); var underlyingSwitchType = byValue.Type.IsEnumType() ? byValue.Type.GetEnumUnderlyingType() : byValue.Type; foreach (var vd in byValue.ValueAndDecision) { var value = vd.Key; var decision = vd.Value; var constantValue = ConstantValue.Create(value, underlyingSwitchType.SpecialType); var constantExpression = new BoundLiteral(_factory.Syntax, constantValue, underlyingSwitchType); var label = _factory.GenerateLabel("case+" + value); var switchLabel = new BoundSwitchLabel(_factory.Syntax, label, constantExpression, constantValue); var forValue = ArrayBuilder <BoundStatement> .GetInstance(); LowerDecisionTree(byValue.Expression, decision, forValue); if (!decision.MatchIsComplete) { forValue.Add(_factory.Goto(noValueMatches)); } var section = new BoundSwitchSection(_factory.Syntax, ImmutableArray.Create(switchLabel), forValue.ToImmutableAndFree()); switchSections.Add(section); } var rewrittenSections = switchSections.ToImmutableAndFree(); MethodSymbol stringEquality = null; if (underlyingSwitchType.SpecialType == SpecialType.System_String) { _localRewriter.EnsureStringHashFunction(rewrittenSections, _factory.Syntax); stringEquality = _localRewriter.UnsafeGetSpecialTypeMethod(_factory.Syntax, SpecialMember.System_String__op_Equality); } // The BoundSwitchStatement requires a constant target when there are no sections, so we accomodate that here. var constantTarget = rewrittenSections.IsEmpty ? noValueMatches : null; var switchStatement = new BoundSwitchStatement( _factory.Syntax, null, _factory.Convert(underlyingSwitchType, byValue.Expression), constantTarget, ImmutableArray <LocalSymbol> .Empty, ImmutableArray <LocalFunctionSymbol> .Empty, rewrittenSections, noValueMatches, stringEquality); // The bound switch statement implicitly defines the label noValueMatches at the end, so we do not add it explicitly. switch (underlyingSwitchType.SpecialType) { case SpecialType.System_Boolean: // boolean switch is handled in LowerBooleanSwitch, not here. throw ExceptionUtilities.Unreachable; case SpecialType.System_String: case SpecialType.System_Byte: case SpecialType.System_Char: case SpecialType.System_Int16: case SpecialType.System_Int32: case SpecialType.System_Int64: case SpecialType.System_SByte: case SpecialType.System_UInt16: case SpecialType.System_UInt32: case SpecialType.System_UInt64: { // emit knows how to efficiently generate code for these kinds of switches. _loweredDecisionTree.Add(switchStatement); break; } default: { // other types, such as float, double, and decimal, are not currently // handled by emit and must be lowered here. _loweredDecisionTree.Add(LowerNonprimitiveSwitch(switchStatement)); break; } } LowerDecisionTree(byValue.Expression, byValue.Default); }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return(AddByValue(byType.Default, value, makeDecision)); } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { return(byType.Expression.ConstantValue?.IsNull == false ? null : AddByNull((DecisionTree)byType, makeDecision)); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { return(null); } continue; case false: case null: continue; } } DecisionTree forType = null; // Find an existing decision tree for the expression's type. Since this new test // should logically be last, we look for the last one we can piggy-back it onto. for (int i = byType.TypeAndDecision.Count - 1; i >= 0 && forType == null; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.TupleUnderlyingTypeOrSelf() == value.Value.Type.TupleUnderlyingTypeOrSelf()) { forType = decision; break; } else if (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics) != false) { break; } } if (forType == null) { var type = value.Value.Type; var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatchingTemp, Syntax, false, RefKind.None); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair <TypeSymbol, DecisionTree>(value.Value.Type, forType)); } return(AddByValue(forType, value, makeDecision)); }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return(AddByValue(byType.Default, value, makeDecision)); } finally { if (byType.Default.MatchIsComplete) { // This code may be unreachable due to https://github.com/dotnet/roslyn/issues/16878 byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { // This should not occur, as the caller will have invoked AddByNull instead. throw ExceptionUtilities.Unreachable; } if ((object)value.Value.Type == null || value.ConstantValue == null) { return(null); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { // Subsumed case have been eliminated by semantic analysis. Debug.Assert(false); return(null); } continue; case false: case null: continue; } } DecisionTree forType = null; // This new type test should logically be last. However it might be the same type as the one that is already // last. In that case we can produce better code by piggy-backing our new case on to the last decision. // Also, the last one might be a non-overlapping type, in which case we can piggy-back onto the second-last // type test. for (int i = byType.TypeAndDecision.Count - 1; i >= 0; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.Equals(value.Value.Type, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) { forType = decision; break; } switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { // we should have reported this case as subsumed already. Debug.Assert(false); return(null); } else { goto case null; } case false: continue; case null: // because there is overlap, we cannot reuse some earlier entry goto noReuse; } } noReuse :; // if we did not piggy-back, then create a new decision tree node for the type. if (forType == null) { var type = value.Value.Type; if (byType.Type.Equals(type, TypeCompareKind.AllIgnoreOptions)) { // reuse the input expression when we have an equivalent type to reduce the number of generated temps forType = new DecisionTree.ByValue(byType.Expression, type.TupleUnderlyingTypeOrSelf(), null); } else { var narrowedExpression = GetBoundPatternMatchingLocal(type); forType = new DecisionTree.ByValue(narrowedExpression, type.TupleUnderlyingTypeOrSelf(), narrowedExpression.LocalSymbol); } byType.TypeAndDecision.Add(new KeyValuePair <TypeSymbol, DecisionTree>(type, forType)); } return(AddByValue(forType, value, makeDecision)); }
private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern value, DecisionMaker makeDecision) { if (byType.Default != null) { try { return AddByValue(byType.Default, value, makeDecision); } finally { if (byType.Default.MatchIsComplete) { byType.MatchIsComplete = true; } } } if (value.ConstantValue == ConstantValue.Null) { return byType.Expression.ConstantValue?.IsNull == false ? null : AddByNull((DecisionTree)byType, makeDecision); } foreach (var kvp in byType.TypeAndDecision) { var matchedType = kvp.Key; var decision = kvp.Value; // See if the test is already subsumed switch (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics)) { case true: if (decision.MatchIsComplete) { return null; } continue; case false: case null: continue; } } DecisionTree forType = null; // Find an existing decision tree for the expression's type. Since this new test // should logically be last, we look for the last one we can piggy-back it onto. for (int i = byType.TypeAndDecision.Count - 1; i >= 0 && forType == null; i--) { var kvp = byType.TypeAndDecision[i]; var matchedType = kvp.Key; var decision = kvp.Value; if (matchedType.TupleUnderlyingTypeOrSelf() == value.Value.Type.TupleUnderlyingTypeOrSelf()) { forType = decision; break; } else if (ExpressionOfTypeMatchesPatternType(value.Value.Type, matchedType, ref _useSiteDiagnostics) != false) { break; } } if (forType == null) { var type = value.Value.Type; var localSymbol = new SynthesizedLocal(_enclosingSymbol as MethodSymbol, type, SynthesizedLocalKind.PatternMatchingTemp, Syntax, false, RefKind.None); var narrowedExpression = new BoundLocal(Syntax, localSymbol, null, type); forType = new DecisionTree.ByValue(narrowedExpression, value.Value.Type.TupleUnderlyingTypeOrSelf(), localSymbol); byType.TypeAndDecision.Add(new KeyValuePair<TypeSymbol, DecisionTree>(value.Value.Type, forType)); } return AddByValue(forType, value, makeDecision); }