Browse Source

Add support for sparse integer switches.

pull/728/merge
Daniel Grunwald 9 years ago
parent
commit
36d61db3a7
  1. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 6
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  3. 2
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  4. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  5. 297
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs
  6. 87
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
  7. 1
      ICSharpCode.Decompiler/IL/Instructions/Branch.cs
  8. 13
      ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs
  9. 86
      ICSharpCode.Decompiler/Tests/Util/LongSetTests.cs
  10. 5
      ICSharpCode.Decompiler/Util/BitSet.cs
  11. 188
      ICSharpCode.Decompiler/Util/Interval.cs
  12. 301
      ICSharpCode.Decompiler/Util/LongSet.cs
  13. 2
      ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -56,6 +56,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -56,6 +56,7 @@ namespace ICSharpCode.Decompiler.CSharp
new ControlFlowSimplification(),
new ILInlining(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new SwitchDetection(),
new LoopDetection(),
new IntroduceExitPoints(),
new ConditionDetection(),

6
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -76,9 +76,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -76,9 +76,9 @@ namespace ICSharpCode.Decompiler.CSharp
value = i != 0;
} else if (type.Kind == TypeKind.Enum) {
var enumType = type.GetDefinition().EnumUnderlyingType;
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, true);
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, false);
} else {
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, true);
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, false);
}
return new CaseLabel(exprBuilder.ConvertConstantValue(new ConstantResolveResult(type, value)));
}
@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp
var stmt = new SwitchStatement() { Expression = value };
foreach (var section in inst.Sections) {
var astSection = new ICSharpCode.NRefactory.CSharp.SwitchSection();
astSection.CaseLabels.AddRange(section.Labels.Range().Select(i => CreateTypedCaseLabel(i, value.Type)));
astSection.CaseLabels.AddRange(section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type)));
astSection.Statements.Add(Convert(section.Body));
stmt.SwitchSections.Add(astSection);
}

2
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -159,7 +159,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -159,7 +159,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
/// the first place.
/// </remarks>
/// <example>
/// The simple state "<c>bool isReachable</c>", would implement <c>ReplaceWith</c> as:
/// The simple state "<c>bool isReachable</c>", would implement <c>ReplaceWithBottom</c> as:
/// <code>this.isReachable = false;</code>
/// </example>
void ReplaceWithBottom();

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -96,6 +96,8 @@ @@ -96,6 +96,8 @@
<Compile Include="IL\ControlFlow\DetectPinnedRegions.cs" />
<Compile Include="IL\ControlFlow\IntroduceExitPoints.cs" />
<Compile Include="IL\ControlFlow\LoopDetection.cs" />
<Compile Include="IL\ControlFlow\SwitchAnalysis.cs" />
<Compile Include="IL\ControlFlow\SwitchDetection.cs" />
<Compile Include="IL\Instructions.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
@ -172,6 +174,7 @@ @@ -172,6 +174,7 @@
<Compile Include="Util\CollectionExtensions.cs" />
<Compile Include="Util\BitSet.cs" />
<Compile Include="Util\Interval.cs" />
<Compile Include="Util\LongSet.cs" />
<Compile Include="Util\UnionFind.cs" />
<None Include="IL\ILOpCodes.tt">
<Generator>TextTemplatingFileGenerator</Generator>
@ -207,7 +210,6 @@ @@ -207,7 +210,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="ILAst\" />
<Folder Include="IL\Patterns" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
<Target Name="BeforeBuild">

297
ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs

@ -0,0 +1,297 @@ @@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
/// <summary>
/// C# switch statements are not necessarily compiled into IL switch instructions.
/// For example, when the label values are not contiguous, the C# compiler
/// will generate if statements similar to a binary search.
///
/// This class analyses such sequences of if statements to reconstruct the original switch.
/// </summary>
/// <remarks>
/// This analysis expects to be run on basic blocks (not extended basic blocks).
/// </remarks>
class SwitchAnalysis
{
/// <summary>
/// The variable that is used to represent the switch expression.
/// <c>null</c> while analyzing the first block.
/// </summary>
ILVariable switchVar;
/// <summary>
/// The variable to be used as the argument of the switch instruction.
/// </summary>
public ILVariable SwitchVariable
{
get { return switchVar; }
}
/// <summary>
/// Gets the sections that were detected by the previoous AnalyzeBlock() call.
/// </summary>
public readonly List<KeyValuePair<LongSet, ILInstruction>> Sections = new List<KeyValuePair<LongSet, ILInstruction>>();
readonly Dictionary<Block, int> targetBlockToSectionIndex = new Dictionary<Block, int>();
/// <summary>
/// Blocks that can be deleted if the tail of the initial block is replaced with a switch instruction.
/// </summary>
public readonly List<Block> InnerBlocks = new List<Block>();
Block rootBlock;
/// <summary>
/// Analyze the last two statements in the block and see if they can be turned into a
/// switch instruction.
/// </summary>
/// <returns>true if the block could be analyzed successfully; false otherwise</returns>
public bool AnalyzeBlock(Block block)
{
switchVar = null;
rootBlock = block;
targetBlockToSectionIndex.Clear();
Sections.Clear();
InnerBlocks.Clear();
return AnalyzeBlock(block, LongSet.Universe, tailOnly: true);
}
/// <summary>
/// Analyzes the tail end (last two instructions) of a block.
/// </summary>
/// <remarks>
/// Sets <c>switchVar</c> and <c>defaultInstruction</c> if they are null,
/// and adds found sections to <c>sectionLabels</c> and <c>sectionInstructions</c>.
///
/// If the function returns false, <c>sectionLabels</c> and <c>sectionInstructions</c> are unmodified.
/// </remarks>
/// <param name="block">The block to analyze.</param>
/// <param name="inputValues">The possible values of the "interesting" variable
/// when control flow reaches this block.</param>
/// <param name="tailOnly">If true, analyze only the tail (last two instructions).
/// If false, analyze the whole block.</param>
bool AnalyzeBlock(Block block, LongSet inputValues, bool tailOnly = false)
{
if (tailOnly) {
Debug.Assert(block == rootBlock);
if (block.Instructions.Count < 2)
return false;
} else {
Debug.Assert(switchVar != null); // switchVar should always be determined by the top-level call
if (block.IncomingEdgeCount != 1 || block == rootBlock)
return false; // for now, let's only consider if-structures that form a tree
if (block.Instructions.Count != 2)
return false;
if (block.Parent != rootBlock.Parent)
return false; // all blocks should belong to the same container
}
var inst = block.Instructions[block.Instructions.Count - 2];
ILInstruction condition, trueInst;
LongSet trueValues;
if (inst.MatchIfInstruction(out condition, out trueInst)
&& AnalyzeCondition(condition, out trueValues)
) {
trueValues = trueValues.IntersectWith(inputValues);
Block trueBlock;
if (trueInst.MatchBranch(out trueBlock) && AnalyzeBlock(trueBlock, trueValues)) {
// OK, true block was further analyzed.
InnerBlocks.Add(trueBlock);
} else {
// Create switch section for trueInst.
AddSection(trueValues, trueInst);
}
} else if (inst.OpCode == OpCode.SwitchInstruction) {
if (AnalyzeSwitch((SwitchInstruction)inst, inputValues, out trueValues)) {
// OK
} else { // switch analysis failed (e.g. switchVar mismatch)
return false;
}
} else { // unknown inst
return false;
}
var remainingValues = inputValues.ExceptWith(trueValues);
ILInstruction falseInst = block.Instructions.Last();
Block falseBlock;
if (falseInst.MatchBranch(out falseBlock) && AnalyzeBlock(falseBlock, remainingValues)) {
// OK, false block was further analyzed.
InnerBlocks.Add(falseBlock);
} else {
// Create switch section for falseInst.
AddSection(remainingValues, falseInst);
}
return true;
}
private bool AnalyzeSwitch(SwitchInstruction inst, LongSet inputValues, out LongSet anyMatchValues)
{
anyMatchValues = LongSet.Empty;
long offset;
if (MatchSwitchVar(inst.Value)) {
offset = 0;
} else if (inst.Value.OpCode == OpCode.BinaryNumericInstruction) {
var bop = (BinaryNumericInstruction)inst.Value;
if (bop.CheckForOverflow)
return false;
long val;
if (MatchSwitchVar(bop.Left) && MatchLdcI(bop.Right, out val)) {
switch (bop.Operator) {
case BinaryNumericOperator.Add:
offset = -val;
break;
case BinaryNumericOperator.Sub:
offset = val;
break;
default: // unknown bop.Operator
return false;
}
} else { // unknown bop.Left
return false;
}
} else { // unknown inst.Value
return false;
}
foreach (var section in inst.Sections) {
var matchValues = section.Labels.AddOffset(offset).IntersectWith(inputValues);
AddSection(matchValues, section.Body);
anyMatchValues = anyMatchValues.UnionWith(matchValues);
}
return true;
}
/// <summary>
/// Adds a new section to the Sections list.
///
/// If the instruction is a branch instruction, unify the new section with an existing section
/// that also branches to the same target.
/// </summary>
void AddSection(LongSet values, ILInstruction inst)
{
Block targetBlock;
if (inst.MatchBranch(out targetBlock)) {
int index;
if (targetBlockToSectionIndex.TryGetValue(targetBlock, out index)) {
Sections[index] = new KeyValuePair<LongSet, ILInstruction>(
Sections[index].Key.UnionWith(values),
inst
);
} else {
targetBlockToSectionIndex.Add(targetBlock, Sections.Count);
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst));
}
} else {
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst));
}
}
bool MatchSwitchVar(ILInstruction inst)
{
if (switchVar != null)
return inst.MatchLdLoc(switchVar);
else
return inst.MatchLdLoc(out switchVar);
}
bool MatchLdcI(ILInstruction inst, out long val)
{
if (inst.MatchLdcI8(out val))
return true;
int intVal;
if (inst.MatchLdcI4(out intVal)) {
val = intVal;
return true;
}
return false;
}
/// <summary>
/// Analyzes the boolean condition, returning the set of values of the interesting
/// variable for which the condition evaluates to true.
/// </summary>
private bool AnalyzeCondition(ILInstruction condition, out LongSet trueValues)
{
ILInstruction arg;
Comp comp = condition as Comp;
long val;
if (comp != null && MatchSwitchVar(comp.Left) && MatchLdcI(comp.Right, out val)) {
// if (comp(V OP val))
switch (comp.Kind) {
case ComparisonKind.Equality:
trueValues = new LongSet(val);
return true;
case ComparisonKind.Inequality:
trueValues = new LongSet(val).Invert();
return true;
case ComparisonKind.LessThan:
trueValues = MakeGreaterThanOrEqualSet(val, comp.Sign).Invert();
return true;
case ComparisonKind.LessThanOrEqual:
trueValues = MakeLessThanOrEqualSet(val, comp.Sign);
return true;
case ComparisonKind.GreaterThan:
trueValues = MakeLessThanOrEqualSet(val, comp.Sign).Invert();
return true;
case ComparisonKind.GreaterThanOrEqual:
trueValues = MakeGreaterThanOrEqualSet(val, comp.Sign);
return true;
default:
trueValues = LongSet.Empty;
return false;
}
} else if (MatchSwitchVar(condition)) {
// if (ldloc V) --> branch for all values except 0
trueValues = new LongSet(0).Invert();
return true;
} else if (condition.MatchLogicNot(out arg)) {
// if (logic.not(X)) --> branch for all values where if (X) does not branch
LongSet falseValues;
bool res = AnalyzeCondition(arg, out falseValues);
trueValues = falseValues.Invert();
return res;
} else {
trueValues = LongSet.Empty;
return false;
}
}
private LongSet MakeGreaterThanOrEqualSet(long val, Sign sign)
{
if (sign == Sign.Signed) {
return new LongSet(LongInterval.Inclusive(val, long.MaxValue));
} else {
Debug.Assert(sign == Sign.Unsigned);
if (val >= 0) {
// The range val to ulong.MaxValue expressed with signed longs
// is not a single contiguous range, but two ranges:
return new LongSet(LongInterval.Inclusive(val, long.MaxValue))
.UnionWith(new LongSet(new LongInterval(long.MinValue, 0)));
} else {
return new LongSet(new LongInterval(val, 0));
}
}
}
private LongSet MakeLessThanOrEqualSet(long val, Sign sign)
{
if (sign == Sign.Signed) {
return new LongSet(LongInterval.Inclusive(long.MinValue, val));
} else {
Debug.Assert(sign == Sign.Unsigned);
if (val >= 0) {
return new LongSet(LongInterval.Inclusive(0, val));
} else {
// The range 0 to (ulong)val expressed with signed longs
// is not a single contiguous range, but two ranges:
return new LongSet(LongInterval.Inclusive(0, long.MaxValue))
.UnionWith(new LongSet(LongInterval.Inclusive(long.MinValue, val)));
}
}
}
}
}

87
ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.Decompiler.IL.Transforms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
/// <summary>
/// C# switch statements are not necessarily compiled into
/// IL switch instructions (e.g. when the integer values are non-contiguous).
///
/// Detect sequences of conditional branches that all test a single integer value,
/// and simplify them into a ILAst switch instruction (which like C# does not require contiguous values).
/// </summary>
class SwitchDetection : IILTransform
{
SwitchAnalysis analysis = new SwitchAnalysis();
public void Run(ILFunction function, ILTransformContext context)
{
foreach (var container in function.Descendants.OfType<BlockContainer>()) {
bool needBlockCleanup = false;
foreach (var block in container.Blocks) {
if (analysis.AnalyzeBlock(block) && UseCSharpSwitch(analysis)) {
var hugeSection = analysis.Sections.Single(s => s.Key.Count() > 50);
var sw = new SwitchInstruction(new LdLoc(analysis.SwitchVariable));
foreach (var section in analysis.Sections) {
if (!section.Key.SetEquals(hugeSection.Key)) {
sw.Sections.Add(new SwitchSection
{
Labels = section.Key,
Body = section.Value
});
}
}
block.Instructions[block.Instructions.Count - 2] = sw;
block.Instructions[block.Instructions.Count - 1] = hugeSection.Value;
// mark all inner blocks that were converted to the switch statement for deletion
foreach (var innerBlock in analysis.InnerBlocks) {
Debug.Assert(innerBlock.Parent == container);
Debug.Assert(innerBlock != container.EntryPoint);
innerBlock.Instructions.Clear();
}
needBlockCleanup = true;
}
}
if (needBlockCleanup) {
Debug.Assert(container.Blocks.All(b => b.Instructions.Count != 0 || b.IncomingEdgeCount == 0));
container.Blocks.RemoveAll(b => b.Instructions.Count == 0);
}
}
}
const ulong MaxValuesPerSection = 50;
/// <summary>
/// Tests whether we should prefer a switch statement over an if statement.
/// </summary>
static bool UseCSharpSwitch(SwitchAnalysis analysis)
{
return analysis.InnerBlocks.Any()
&& analysis.Sections.Count(s => s.Key.Count() > MaxValuesPerSection) == 1;
}
}
}

1
ICSharpCode.Decompiler/IL/Instructions/Branch.cs

@ -107,6 +107,7 @@ namespace ICSharpCode.Decompiler.IL @@ -107,6 +107,7 @@ namespace ICSharpCode.Decompiler.IL
if (phase > ILPhase.InILReader) {
Debug.Assert(targetBlock.Parent is BlockContainer);
Debug.Assert(this.IsDescendantOf(targetBlock.Parent));
Debug.Assert(targetBlock.Parent.Children[targetBlock.ChildIndex] == targetBlock);
}
}

13
ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs

@ -24,7 +24,11 @@ using System.Linq; @@ -24,7 +24,11 @@ using System.Linq;
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// Description of SwitchInstruction.
/// Generalization of IL switch-case: like a VB switch over integers, this instruction
/// supports integer value ranges as labels.
///
/// The section labels are using 'long' as integer type.
/// If the Value instruction produces StackType.I4 or I, the value is implicitly sign-extended to I8.
/// </summary>
partial class SwitchInstruction
{
@ -52,6 +56,7 @@ namespace ICSharpCode.Decompiler.IL @@ -52,6 +56,7 @@ namespace ICSharpCode.Decompiler.IL
protected override InstructionFlags ComputeFlags()
{
var sectionFlags = InstructionFlags.ControlFlow;
// note: the initial sectionFlags also represent the implicit empty 'default' case
foreach (var section in Sections) {
sectionFlags = SemanticHelper.CombineBranches(sectionFlags, section.Flags);
}
@ -120,9 +125,9 @@ namespace ICSharpCode.Decompiler.IL @@ -120,9 +125,9 @@ namespace ICSharpCode.Decompiler.IL
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
LongSet sets = new LongSet(ImmutableArray<LongInterval>.Empty);
LongSet sets = LongSet.Empty;
foreach (var section in Sections) {
Debug.Assert(!section.Labels.Intersects(sets));
Debug.Assert(!section.Labels.Overlaps(sets));
sets = sets.UnionWith(section.Labels);
}
}
@ -133,7 +138,7 @@ namespace ICSharpCode.Decompiler.IL @@ -133,7 +138,7 @@ namespace ICSharpCode.Decompiler.IL
public SwitchSection()
: base(OpCode.SwitchSection)
{
this.Labels = LongSet.Empty;
}
public LongSet Labels { get; set; }

86
ICSharpCode.Decompiler/Tests/Util/LongSetTests.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using NUnit.Framework;
namespace ICSharpCode.Decompiler.Tests.Util
@ -28,12 +29,95 @@ namespace ICSharpCode.Decompiler.Tests.Util @@ -28,12 +29,95 @@ namespace ICSharpCode.Decompiler.Tests.Util
[Test]
public void UpperBound()
{
var longSet = new LongSet(new [] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray());
var longSet = new LongSet(new[] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray());
Assert.AreEqual(0, longSet.upper_bound(0));
for (int i = 1; i <= 5; i++)
Assert.AreEqual(1, longSet.upper_bound(i));
for (int i = 6; i <= 10; i++)
Assert.AreEqual(2, longSet.upper_bound(i));
}
[Test]
public void UniverseContainsAll()
{
Assert.IsTrue(LongSet.Universe.Contains(long.MinValue));
Assert.IsTrue(LongSet.Universe.Contains(1));
Assert.IsTrue(LongSet.Universe.Contains(long.MaxValue));
Assert.IsFalse(LongSet.Universe.IsEmpty);
}
[Test]
public void IntersectUniverse()
{
Assert.AreEqual(LongSet.Universe, LongSet.Universe.IntersectWith(LongSet.Universe));
Assert.AreEqual(LongSet.Empty, LongSet.Universe.IntersectWith(LongSet.Empty));
Assert.AreEqual(new LongSet(long.MaxValue), LongSet.Universe.IntersectWith(new LongSet(long.MaxValue)));
var longSet = new LongSet(new[] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray());
Assert.AreEqual(longSet, longSet.IntersectWith(LongSet.Universe));
}
[Test]
public void UnionUniverse()
{
Assert.AreEqual(LongSet.Universe, LongSet.Universe.UnionWith(LongSet.Universe));
Assert.AreEqual(LongSet.Universe, LongSet.Universe.UnionWith(LongSet.Empty));
Assert.AreEqual(LongSet.Universe, LongSet.Universe.UnionWith(new LongSet(long.MaxValue)));
var longSet = new LongSet(new[] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray());
Assert.AreEqual(LongSet.Universe, longSet.UnionWith(LongSet.Universe));
}
[Test]
public void ExceptWithUniverse()
{
Assert.AreEqual(LongSet.Universe, LongSet.Universe.ExceptWith(LongSet.Empty));
Assert.AreEqual(LongSet.Empty, LongSet.Universe.ExceptWith(LongSet.Universe));
Assert.AreEqual(LongSet.Empty, LongSet.Empty.ExceptWith(LongSet.Universe));
Assert.AreEqual(LongSet.Empty, LongSet.Empty.ExceptWith(LongSet.Empty));
}
[Test]
public void UnionWith()
{
Assert.AreEqual(new LongSet(new LongInterval(0, 2)),
new LongSet(0).UnionWith(new LongSet(1)));
Assert.AreEqual(LongSet.Universe, new LongSet(0).Invert().UnionWith(new LongSet(0)));
}
[Test]
public void AddTo()
{
Assert.AreEqual(new LongSet(1), new LongSet(0).AddOffset(1));
Assert.AreEqual(new LongSet(long.MinValue), new LongSet(long.MaxValue).AddOffset(1));
TestAddTo(new LongSet(new LongInterval(-10, 10)), 5);
TestAddTo(new LongSet(new LongInterval(-10, 10)), long.MaxValue);
Assert.AreEqual(new LongSet(10).Invert(), new LongSet(0).Invert().AddOffset(10));
Assert.AreEqual(new LongSet(20).Invert(), new LongSet(30).Invert().AddOffset(-10));
}
void TestAddTo(LongSet input, long constant)
{
Assert.AreEqual(
input.Values.Select(e => unchecked(e + constant)).OrderBy(e => e).ToList(),
input.AddOffset(constant).Values.ToList());
}
[Test]
public void Values()
{
Assert.IsFalse(LongSet.Empty.Values.Any());
Assert.IsTrue(LongSet.Universe.Values.Any());
Assert.AreEqual(new[] { 1, 2, 3 }, new LongSet(LongInterval.Inclusive(1, 3)).Values.ToArray());
}
[Test]
public void ValueCount()
{
Assert.AreEqual(0, LongSet.Empty.Count());
Assert.AreEqual(ulong.MaxValue, new LongSet(3).Invert().Count());
Assert.AreEqual(ulong.MaxValue, LongSet.Universe.Count());
Assert.AreEqual(long.MaxValue + 2ul, new LongSet(LongInterval.Inclusive(-1, long.MaxValue)).Count());
}
}
}

5
ICSharpCode.Decompiler/Util/BitSet.cs

@ -17,9 +17,6 @@ @@ -17,9 +17,6 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
@ -124,7 +121,7 @@ namespace ICSharpCode.Decompiler @@ -124,7 +121,7 @@ namespace ICSharpCode.Decompiler
public bool IsProperSupersetOf(BitSet other)
{
return IsSubsetOf(other) && !SetEquals(other);
return IsSupersetOf(other) && !SetEquals(other);
}
/// <summary>

188
ICSharpCode.Decompiler/Util/Interval.cs

@ -1,11 +1,23 @@ @@ -1,11 +1,23 @@
using System;
using System.Collections;
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler
{
@ -133,7 +145,7 @@ namespace ICSharpCode.Decompiler @@ -133,7 +145,7 @@ namespace ICSharpCode.Decompiler
/// <remarks>
/// Start &lt;= unchecked(End - 1): normal interval
/// Start == End: empty interval
/// Special case: Start == End == int.MinValue: interval containing all integers, not an empty interval!
/// Special case: Start == End == long.MinValue: interval containing all integers, not an empty interval!
/// </remarks>
public struct LongInterval : IEquatable<LongInterval>
{
@ -151,14 +163,19 @@ namespace ICSharpCode.Decompiler @@ -151,14 +163,19 @@ namespace ICSharpCode.Decompiler
/// If possible, prefer using InclusiveEnd for comparisons, as that does not have an overflow problem.
/// </remarks>
public readonly long End;
/// <summary>
/// Creates a new interval.
/// </summary>
/// <param name="start">Start position (inclusive)</param>
/// <param name="end">End position (exclusive).
/// Note that it is possible to create an interval that includes int.MaxValue
/// by using end==int.MaxValue+1==int.MinValue.</param>
/// Note that it is possible to create an interval that includes long.MaxValue
/// by using end==long.MaxValue+1==long.MinValue.</param>
/// <remarks>
/// This method can be used to create an empty interval by specifying start==end,
/// however this is error-prone due to the special case of
/// start==end==long.MinValue being interpreted as the full interval [long.MinValue,long.MaxValue].
/// </remarks>
public LongInterval(long start, long end)
{
if (!(start <= unchecked(end - 1) || start == end))
@ -166,6 +183,20 @@ namespace ICSharpCode.Decompiler @@ -166,6 +183,20 @@ namespace ICSharpCode.Decompiler
this.Start = start;
this.End = end;
}
/// <summary>
/// Creates a new interval from start to end.
/// Unlike the constructor where the end position is exclusive,
/// this method interprets the end position as inclusive.
///
/// This method cannot be used to construct an empty interval.
/// </summary>
public static LongInterval Inclusive(long start, long inclusiveEnd)
{
if (!(start <= inclusiveEnd))
throw new ArgumentException();
return new LongInterval(start, unchecked(inclusiveEnd + 1));
}
/// <summary>
/// Gets the inclusive end of the interval. (End - 1)
@ -190,7 +221,7 @@ namespace ICSharpCode.Decompiler @@ -190,7 +221,7 @@ namespace ICSharpCode.Decompiler
public bool Contains(long val)
{
// Use 'val <= InclusiveEnd' instead of 'val < End' to allow intervals to include int.MaxValue.
// Use 'val <= InclusiveEnd' instead of 'val < End' to allow intervals to include long.MaxValue.
return Start <= val && val <= InclusiveEnd;
}
@ -261,137 +292,4 @@ namespace ICSharpCode.Decompiler @@ -261,137 +292,4 @@ namespace ICSharpCode.Decompiler
}
#endregion
}
/// <summary>
/// An immutable set of longs, that is implemented as a list of intervals.
/// </summary>
public struct LongSet
{
public readonly ImmutableArray<LongInterval> Intervals;
public LongSet(ImmutableArray<LongInterval> intervals)
{
this.Intervals = intervals;
}
public LongSet(long value)
: this(ImmutableArray.Create(new LongInterval(value, unchecked(value + 1))))
{
}
public bool IsEmpty
{
get { return Intervals.IsDefaultOrEmpty; }
}
IEnumerable<LongInterval> DoIntersectWith(LongSet other)
{
var enumA = this.Intervals.GetEnumerator();
var enumB = other.Intervals.GetEnumerator();
bool moreA = enumA.MoveNext();
bool moreB = enumB.MoveNext();
while (moreA && moreB) {
LongInterval a = enumA.Current;
LongInterval b = enumB.Current;
LongInterval intersection = a.Intersect(b);
if (!intersection.IsEmpty) {
yield return intersection;
}
if (a.InclusiveEnd < b.InclusiveEnd) {
moreA = enumA.MoveNext();
} else {
moreB = enumB.MoveNext();
}
}
}
public bool Intersects(LongSet other)
{
return DoIntersectWith(other).Any();
}
public LongSet IntersectWith(LongSet other)
{
return new LongSet(DoIntersectWith(other).ToImmutableArray());
}
IEnumerable<LongInterval> DoUnionWith(LongSet other)
{
long start = long.MinValue;
long end = long.MinValue;
bool empty = true;
foreach (var element in this.Intervals.Merge(other.Intervals, (a, b) => a.Start.CompareTo(b.Start))) {
Debug.Assert(start <= element.Start);
if (!empty && element.Start < end - 1) {
// element overlaps or touches [start, end), so combine the intervals:
if (element.End == long.MinValue) {
// special case: element goes all the way up to long.MaxValue inclusive
yield return new LongInterval(start, element.End);
break;
} else {
end = Math.Max(end, element.End);
}
} else {
// flush existing interval:
if (!empty) {
yield return new LongInterval(start, end);
} else {
empty = false;
}
start = element.Start;
end = element.End;
}
if (end == long.MinValue) {
// special case: element goes all the way up to long.MaxValue inclusive
// all further intervals in the input must be contained in [start, end),
// so ignore them (and avoid trouble due to the overflow in `end`).
break;
}
}
if (!empty) {
yield return new LongInterval(start, end);
}
}
public LongSet UnionWith(LongSet other)
{
return new LongSet(DoUnionWith(other).ToImmutableArray());
}
public bool Contains(long val)
{
int index = upper_bound(val);
return index > 0 && Intervals[index - 1].Contains(val);
}
internal int upper_bound(long val)
{
int min = 0, max = Intervals.Length - 1;
while (max >= min) {
int m = min + (max - min) / 2;
LongInterval i = Intervals[m];
if (val < i.Start) {
max = m - 1;
continue;
}
if (val > i.End) {
min = m + 1;
continue;
}
return m + 1;
}
return min;
}
public IEnumerable<long> Range()
{
return Intervals.SelectMany(i => i.Range());
}
public override string ToString()
{
return string.Join(",", Intervals);
}
}
}

301
ICSharpCode.Decompiler/Util/LongSet.cs

@ -0,0 +1,301 @@ @@ -0,0 +1,301 @@
// Copyright (c) 2016 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
namespace ICSharpCode.Decompiler
{
/// <summary>
/// An immutable set of longs, that is implemented as a list of intervals.
/// </summary>
public struct LongSet : IEquatable<LongSet>
{
public readonly ImmutableArray<LongInterval> Intervals;
public LongSet(ImmutableArray<LongInterval> intervals)
{
this.Intervals = intervals;
#if DEBUG
long minValue = long.MinValue;
for (int i = 0; i < intervals.Length; i++) {
Debug.Assert(!intervals[i].IsEmpty);
Debug.Assert(minValue <= intervals[i].Start);
if (intervals[i].InclusiveEnd == long.MaxValue - 1 || intervals[i].InclusiveEnd == long.MaxValue) {
// An inclusive end of long.MaxValue-1 or long.MaxValue means (after the gap of 1 element),
// there isn't any room for more non-empty intervals.
Debug.Assert(i == intervals.Length - 1);
} else {
minValue = checked(intervals[i].End + 1); // enforce least 1 gap between intervals
}
}
#endif
}
/// <summary>
/// Create a new LongSet that contains a single value.
/// </summary>
public LongSet(long value)
: this(ImmutableArray.Create(LongInterval.Inclusive(value, value)))
{
}
/// <summary>
/// Create a new LongSet that contains the values from the interval.
/// </summary>
public LongSet(LongInterval interval)
: this(interval.IsEmpty ? Empty.Intervals : ImmutableArray.Create(interval))
{
}
/// <summary>
/// The empty LongSet.
/// </summary>
public static readonly LongSet Empty = new LongSet(ImmutableArray.Create<LongInterval>());
/// <summary>
/// The LongSet that contains all possible long values.
/// </summary>
public static readonly LongSet Universe = new LongSet(LongInterval.Inclusive(long.MinValue, long.MaxValue));
public bool IsEmpty
{
get { return Intervals.IsEmpty; }
}
/// <summary>
/// Gets the number of values in this LongSet.
/// Note: for <c>LongSet.Universe</c>, the number of values does not fit into <c>ulong</c>.
/// Instead, this property returns the off-by-one value <c>ulong.MaxValue</c> to avoid overflow.
/// </summary>
public ulong Count()
{
unchecked {
ulong count = 0;
foreach (var interval in Intervals) {
count += (ulong)(interval.End - interval.Start);
}
if (count == 0 && !Intervals.IsEmpty)
return ulong.MaxValue;
else
return count;
}
}
IEnumerable<LongInterval> DoIntersectWith(LongSet other)
{
var enumA = this.Intervals.GetEnumerator();
var enumB = other.Intervals.GetEnumerator();
bool moreA = enumA.MoveNext();
bool moreB = enumB.MoveNext();
while (moreA && moreB) {
LongInterval a = enumA.Current;
LongInterval b = enumB.Current;
LongInterval intersection = a.Intersect(b);
if (!intersection.IsEmpty) {
yield return intersection;
}
if (a.InclusiveEnd < b.InclusiveEnd) {
moreA = enumA.MoveNext();
} else {
moreB = enumB.MoveNext();
}
}
}
public bool Overlaps(LongSet other)
{
return DoIntersectWith(other).Any();
}
public LongSet IntersectWith(LongSet other)
{
return new LongSet(DoIntersectWith(other).ToImmutableArray());
}
/// <summary>
/// Given an enumerable of non-empty intervals sorted by the starting position,
/// merges overlapping or touching intervals to create a valid interval array for LongSet.
/// </summary>
static IEnumerable<LongInterval> MergeOverlapping(IEnumerable<LongInterval> input)
{
long start = long.MinValue;
long end = long.MinValue;
bool empty = true;
foreach (var element in input) {
Debug.Assert(start <= element.Start);
Debug.Assert(!element.IsEmpty);
if (!empty && element.Start <= end) {
// element overlaps or touches [start, end), so combine the intervals:
if (element.End == long.MinValue) {
// special case: element goes all the way up to long.MaxValue inclusive
end = long.MinValue;
} else {
end = Math.Max(end, element.End);
}
} else {
// flush existing interval:
if (!empty) {
yield return new LongInterval(start, end);
} else {
empty = false;
}
start = element.Start;
end = element.End;
}
if (end == long.MinValue) {
// special case: element goes all the way up to long.MaxValue inclusive
// all further intervals in the input must be contained in [start, end),
// so ignore them (and avoid trouble due to the overflow in `end`).
break;
}
}
if (!empty) {
yield return new LongInterval(start, end);
}
}
public LongSet UnionWith(LongSet other)
{
var mergedIntervals = this.Intervals.Merge(other.Intervals, (a, b) => a.Start.CompareTo(b.Start));
return new LongSet(MergeOverlapping(mergedIntervals).ToImmutableArray());
}
/// <summary>
/// Creates a new LongSet where val is added to each element of this LongSet.
/// </summary>
public LongSet AddOffset(long val)
{
var newIntervals = new List<LongInterval>(Intervals.Length + 1);
foreach (var element in Intervals) {
long newStart = unchecked(element.Start + val);
long newInclusiveEnd = unchecked(element.InclusiveEnd + val);
if (newStart <= newInclusiveEnd) {
newIntervals.Add(LongInterval.Inclusive(newStart, newInclusiveEnd));
} else {
// interval got split by integer overflow
newIntervals.Add(LongInterval.Inclusive(newStart, long.MaxValue));
newIntervals.Add(LongInterval.Inclusive(long.MinValue, newInclusiveEnd));
}
}
newIntervals.Sort((a, b) => a.Start.CompareTo(b.Start));
return new LongSet(MergeOverlapping(newIntervals).ToImmutableArray());
}
/// <summary>
/// Creates a new set that contains all values that are in <c>this</c>, but not in <c>other</c>.
/// </summary>
public LongSet ExceptWith(LongSet other)
{
return IntersectWith(other.Invert());
}
/// <summary>
/// Creates a new LongSet that contains all elements not contained in this LongSet.
/// </summary>
public LongSet Invert()
{
// The loop below assumes a non-empty LongSet, so handle the empty case specially.
if (IsEmpty) {
return Universe;
}
List<LongInterval> newIntervals = new List<LongInterval>(Intervals.Length + 1);
long prevEnd = long.MinValue; // previous exclusive end
foreach (var interval in Intervals) {
if (interval.Start > prevEnd) {
newIntervals.Add(new LongInterval(prevEnd, interval.Start));
}
prevEnd = interval.End;
}
// create a final interval up to long.MaxValue inclusive
if (prevEnd != long.MinValue) {
newIntervals.Add(new LongInterval(prevEnd, long.MinValue));
}
return new LongSet(newIntervals.ToImmutableArray());
}
public bool Contains(long val)
{
int index = upper_bound(val);
return index > 0 && Intervals[index - 1].Contains(val);
}
internal int upper_bound(long val)
{
int min = 0, max = Intervals.Length - 1;
while (max >= min) {
int m = min + (max - min) / 2;
LongInterval i = Intervals[m];
if (val < i.Start) {
max = m - 1;
continue;
}
if (val > i.End) {
min = m + 1;
continue;
}
return m + 1;
}
return min;
}
public IEnumerable<long> Values
{
get { return Intervals.SelectMany(i => i.Range()); }
}
public override string ToString()
{
return string.Join(",", Intervals);
}
#region Equals and GetHashCode implementation
public override bool Equals(object obj)
{
return obj is LongSet && SetEquals((LongSet)obj);
}
public override int GetHashCode()
{
throw new NotImplementedException();
}
[Obsolete("Explicitly call SetEquals() instead.")]
public bool Equals(LongSet other)
{
return SetEquals(other);
}
public bool SetEquals(LongSet other)
{
if (Intervals.Length != other.Intervals.Length)
return false;
for (int i = 0; i < Intervals.Length; i++) {
if (Intervals[i] != other.Intervals[i])
return false;
}
return true;
}
#endregion
}
}

2
ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs

@ -67,7 +67,7 @@ namespace ILSpy.BamlDecompiler @@ -67,7 +67,7 @@ namespace ILSpy.BamlDecompiler
if (ilSwitch != null) {
foreach (var section in ilSwitch.Sections) {
var events = FindEvents(section.Body);
foreach (long id in section.Labels.Range())
foreach (long id in section.Labels.Values)
result.Add(id, events);
}
} else {

Loading…
Cancel
Save