diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index dd79dd2e2..f54007484 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -290,6 +290,12 @@ namespace Decompiler new Ast.GotoStatement(((ILLabel)byteCode.Operand).Name) } }; + case ILCode.TernaryOp: + return new Ast.ConditionalExpression() { + Condition = MakeBranchCondition(byteCode.Arguments[0]), + TrueExpression = (Expression)TransformExpression(byteCode.Arguments[1]), + FalseExpression = (Expression)TransformExpression(byteCode.Arguments[2]), + }; } List args = TransformExpressionArguments(byteCode); diff --git a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs index 0d7c3f220..7cbbb7d3b 100644 --- a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs +++ b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs @@ -57,7 +57,7 @@ namespace ICSharpCode.Decompiler.Disassembler { writer.WriteDefinition(CecilExtensions.OffsetToString(instruction.Offset), instruction); writer.Write(": "); - writer.Write(instruction.OpCode.Name); + writer.WriteReference(instruction.OpCode.Name, instruction.OpCode); if(null != instruction.Operand) { writer.Write(' '); WriteOperand(writer, instruction.Operand); diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index f2e64e438..6fa5b760e 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -12,6 +12,7 @@ namespace Decompiler.ControlFlow public enum ILAstOptimizationStep { SplitToMovableBlocks, + PeepholeOptimizations, FindLoops, FindConditions, FlattenNestedMovableBlocks, @@ -33,7 +34,11 @@ namespace Decompiler.ControlFlow SplitToBasicBlocks(block); } - OptimizeShortCircuits(method); + if (abortBeforeStep == ILAstOptimizationStep.PeepholeOptimizations) return; + AnalyseLabels(method); + foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { + PeepholeOptimizations(block); + } if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { @@ -100,24 +105,23 @@ namespace Decompiler.ControlFlow ILNode lastNode = block.Body[i - 1]; ILNode currNode = block.Body[i]; - bool added = false; - // Insert split if (currNode is ILLabel || lastNode is ILTryCatchBlock || currNode is ILTryCatchBlock || (lastNode is ILExpression) && ((ILExpression)lastNode).IsBranch() || - (currNode is ILExpression) && (((ILExpression)currNode).IsBranch() && basicBlock.Body.Count > 0)) + (currNode is ILExpression) && (((ILExpression)currNode).IsBranch() && ((ILExpression)currNode).Code.CanFallThough() && basicBlock.Body.Count > 0)) { ILBasicBlock lastBlock = basicBlock; basicBlock = new ILBasicBlock(); basicBlocks.Add(basicBlock); + if (currNode is ILLabel) { - // Reuse the first label + // Insert as entry label basicBlock.EntryLabel = (ILLabel)currNode; - added = true; } else { basicBlock.EntryLabel = new ILLabel() { Name = "Block_" + (nextBlockIndex++) }; + basicBlock.Body.Add(currNode); } // Explicit branch from one block to other @@ -125,10 +129,20 @@ namespace Decompiler.ControlFlow if (!(lastNode is ILExpression) || ((ILExpression)lastNode).Code.CanFallThough()) { lastBlock.FallthoughGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel); } + } else { + basicBlock.Body.Add(currNode); } - - if (!added) - basicBlock.Body.Add(currNode); + } + } + + foreach (ILBasicBlock bb in basicBlocks) { + if (bb.Body.Count > 0 && + bb.Body.Last() is ILExpression && + ((ILExpression)bb.Body.Last()).Code == ILCode.Br) + { + Debug.Assert(bb.FallthoughGoto == null); + bb.FallthoughGoto = (ILExpression)bb.Body.Last(); + bb.Body.RemoveAt(bb.Body.Count - 1); } } @@ -136,25 +150,6 @@ namespace Decompiler.ControlFlow return; } - void OptimizeShortCircuits(ILBlock method) - { - AnalyseLabels(method); - - foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { - bool modified; - do { - modified = false; - for (int i = 0; i < block.Body.Count;) { - if (TrySimplifyShortCircuit(block.Body, (ILBasicBlock)block.Body[i])) { - modified = true; - } else { - i++; - } - } - } while(modified); - } - } - Dictionary labelGlobalRefCount; Dictionary labelToBasicBlock; @@ -175,11 +170,34 @@ namespace Decompiler.ControlFlow } } + void PeepholeOptimizations(ILBlock block) + { + bool modified; + do { + modified = false; + for (int i = 0; i < block.Body.Count;) { + if (TrySimplifyShortCircuit(block.Body, (ILBasicBlock)block.Body[i])) { + modified = true; + continue; + } + if (TrySimplifyTernaryOperator(block.Body, (ILBasicBlock)block.Body[i])) { + modified = true; + continue; + } + i++; + } + } while(modified); + } + bool IsConditionalBranch(ILBasicBlock bb, ref ILExpression branchExpr, ref ILLabel trueLabel, ref ILLabel falseLabel) { if (bb.Body.Count == 1) { branchExpr = bb.Body[0] as ILExpression; - if (branchExpr != null && branchExpr.Operand is ILLabel && branchExpr.Arguments.Count > 0) { + if (branchExpr != null && + branchExpr.Operand is ILLabel && + branchExpr.Arguments.Count > 0 && + branchExpr.Prefixes == null) + { trueLabel = (ILLabel)branchExpr.Operand; falseLabel = (ILLabel)((ILExpression)bb.FallthoughGoto).Operand; return true; @@ -188,6 +206,71 @@ namespace Decompiler.ControlFlow return false; } + bool IsStloc(ILBasicBlock bb, ref ILVariable locVar, ref ILExpression val, ref ILLabel fallLabel) + { + if (bb.Body.Count == 1) { + ILExpression expr = bb.Body[0] as ILExpression; + if (expr != null && + expr.Code == ILCode.Stloc && + expr.Prefixes == null) + { + locVar = (ILVariable)expr.Operand; + val = expr.Arguments[0]; + fallLabel = (ILLabel)bb.FallthoughGoto.Operand; + return true; + } + } + return false; + } + + // scope is modified if successful + bool TrySimplifyTernaryOperator(List scope, ILBasicBlock head) + { + Debug.Assert(scope.Contains(head)); + + ILExpression branchExpr = null; + ILLabel trueLabel = null; + ILLabel falseLabel = null; + ILVariable trueLocVar = null; + ILExpression trueExpr = null; + ILLabel trueFall = null; + ILVariable falseLocVar = null; + ILExpression falseExpr = null; + ILLabel falseFall = null; + + if(IsConditionalBranch(head, ref branchExpr, ref trueLabel, ref falseLabel) && + labelGlobalRefCount[trueLabel] == 1 && + labelGlobalRefCount[falseLabel] == 1 && + IsStloc(labelToBasicBlock[trueLabel], ref trueLocVar, ref trueExpr, ref trueFall) && + IsStloc(labelToBasicBlock[falseLabel], ref falseLocVar, ref falseExpr, ref falseFall) && + trueLocVar == falseLocVar && + trueFall == falseFall) + { + // Create the ternary expression + head.Body = new List() { + new ILExpression(ILCode.Stloc, trueLocVar, + new ILExpression(ILCode.TernaryOp, null, + new ILExpression(branchExpr.Code, null, branchExpr.Arguments.ToArray()), + trueExpr, + falseExpr + ) + ) + }; + head.FallthoughGoto = new ILExpression(ILCode.Br, trueFall); + + // Remove the old basic blocks + scope.Remove(labelToBasicBlock[trueLabel]); + scope.Remove(labelToBasicBlock[falseLabel]); + labelToBasicBlock.Remove(trueLabel); + labelToBasicBlock.Remove(falseLabel); + labelGlobalRefCount.Remove(trueLabel); + labelGlobalRefCount.Remove(falseLabel); + + return true; + } + return false; + } + // scope is modified if successful bool TrySimplifyShortCircuit(List scope, ILBasicBlock head) { @@ -228,7 +311,8 @@ namespace Decompiler.ControlFlow head.FallthoughGoto = new ILExpression(ILCode.Br, nextFalseLabel); // Remove the inlined branch from scope - labelGlobalRefCount[nextBasicBlock.EntryLabel] = 0; + labelGlobalRefCount.Remove(nextBasicBlock.EntryLabel); + labelToBasicBlock.Remove(nextBasicBlock.EntryLabel); if (!scope.Remove(nextBasicBlock)) throw new Exception("Element not found"); diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 8010821ba..75f9b1239 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -259,6 +259,7 @@ namespace Decompiler BrLogicAnd, BrLogicOr, InitArray, // Array Initializer + TernaryOp, // ?: Pattern // used for ILAst pattern nodes } diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 1cbc9d441..14a4c346e 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -119,6 +119,14 @@ namespace Decompiler InferTypeForExpression(expr.Arguments[1], typeSystem.Boolean); } return null; + case ILCode.TernaryOp: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean); + } + return TypeWithMoreInformation( + InferTypeForExpression(expr.Arguments[1], expectedType, forceInferChildren), + InferTypeForExpression(expr.Arguments[2], expectedType, forceInferChildren) + ); #endregion #region Variable load/store case ILCode.Stloc: diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 8f160c5cd..5079d6078 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -280,6 +280,13 @@ namespace ICSharpCode.ILSpy SelectNode(assemblyListTreeNode.FindEventNode(((EventReference)reference).Resolve())); } else if (reference is AssemblyDefinition) { SelectNode(assemblyListTreeNode.FindAssemblyNode((AssemblyDefinition)reference)); + } else if (reference is Mono.Cecil.Cil.OpCode) { + string link = "http://msdn.microsoft.com/library/system.reflection.emit.opcodes." + ((Mono.Cecil.Cil.OpCode)reference).Code.ToString().ToLowerInvariant() + ".aspx"; + try { + Process.Start(link); + } catch { + + } } } #endregion diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index a9b141ef5..fd100a69c 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -19,27 +19,31 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media.Animation; using System.Windows.Threading; using System.Xml; -using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.NRefactory.Documentation; using ILSpy.Debugger.AvalonEdit; -using ILSpy.Debugger.Bookmarks; using ILSpy.Debugger.ToolTips; using Microsoft.Win32; -using Mono.Cecil; + +using TextEditorWeakEventManager = ILSpy.Debugger.AvalonEdit.TextEditorWeakEventManager; namespace ICSharpCode.ILSpy.TextView { @@ -73,11 +77,13 @@ namespace ICSharpCode.ILSpy.TextView }); InitializeComponent(); - this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference); + this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); this.uiElementGenerator = new UIElementGenerator(); textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator); textEditor.Options.RequireControlModifierForHyperlinkClick = false; + textEditor.TextArea.TextView.MouseHover += TextViewMouseHover; + textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped; // add margin iconMargin = new IconBarMargin(); @@ -92,6 +98,92 @@ namespace ICSharpCode.ILSpy.TextView #endregion + #region Tooltip support + ToolTip tooltip; + + void TextViewMouseHoverStopped(object sender, MouseEventArgs e) + { + if (tooltip != null) + tooltip.IsOpen = false; + } + + void TextViewMouseHover(object sender, MouseEventArgs e) + { + TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset); + if (position == null) + return; + int offset = textEditor.Document.GetOffset(position.Value); + ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault(); + if (seg == null) + return; + object content = GenerateTooltip(seg); + if (tooltip != null) + tooltip.IsOpen = false; + if (content != null) + tooltip = new ToolTip() { Content = content, IsOpen = true }; + } + + object GenerateTooltip(ReferenceSegment segment) + { + if (segment.Reference is Mono.Cecil.Cil.OpCode) { + Mono.Cecil.Cil.OpCode code = (Mono.Cecil.Cil.OpCode)segment.Reference; + string encodedName = code.Code.ToString(); + string opCodeHex = code.Size > 1 ? string.Format("0x{0:x2}{1:x2}", code.Op1, code.Op2) : string.Format("0x{0:x2}", code.Op2); + string documentationFile = FindDocumentation("mscorlib.xml"); + string text = ""; + if (documentationFile != null){ + XmlDocumentationProvider provider = new XmlDocumentationProvider(documentationFile); + string documentation = provider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + encodedName); + if (documentation != null) + text = StripXml(documentation); + } + return string.Format("{0} ({1}): {2}", code.Name, opCodeHex, text); + } + + return null; + } + + string StripXml(string xml) + { + return Regex.Replace(xml, "", "").Trim(); + } + + string FindDocumentation(string fileName) + { + string path = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); + List names = new List(); + EnumerateCultures(CultureInfo.CurrentCulture, names); + names.Add("en"); + names.Add("en-US"); + names.Add("en-GB"); + + foreach (string name in names) { + string location = Path.Combine(path, name, fileName); + if (File.Exists(location)) + return location; + } + + path = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") ?? Environment.GetEnvironmentVariable("PROGRAMFILES"), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"); + + string loc = Path.Combine(path, fileName); + + if (File.Exists(loc)) + return loc; + + return null; + } + + void EnumerateCultures(CultureInfo info, List names) + { + while (info != null) { + names.Add(info.Name); + info = info.Parent; + if (info == info.Parent) + return; + } + } + #endregion + #region RunWithCancellation /// /// Switches the GUI into "waiting" mode, then calls to create @@ -381,6 +473,14 @@ namespace ICSharpCode.ILSpy.TextView } mainWindow.JumpToReference(reference); } + + /// + /// Filters all ReferenceSegments that are no real links. + /// + bool IsLink(ReferenceSegment referenceSegment) + { + return true; + } #endregion #region SaveToDisk diff --git a/ILSpy/TextView/ReferenceElementGenerator.cs b/ILSpy/TextView/ReferenceElementGenerator.cs index 09e0e00a6..e83d89f64 100644 --- a/ILSpy/TextView/ReferenceElementGenerator.cs +++ b/ILSpy/TextView/ReferenceElementGenerator.cs @@ -29,17 +29,21 @@ namespace ICSharpCode.ILSpy.TextView sealed class ReferenceElementGenerator : VisualLineElementGenerator { Action referenceClicked; + Predicate isLink; /// /// The collection of references (hyperlinks). /// public TextSegmentCollection References { get; set; } - public ReferenceElementGenerator(Action referenceClicked) + public ReferenceElementGenerator(Action referenceClicked, Predicate isLink) { if (referenceClicked == null) throw new ArgumentNullException("referenceClicked"); + if (isLink == null) + throw new ArgumentNullException("isLink"); this.referenceClicked = referenceClicked; + this.isLink = isLink; } public override int GetFirstInterestedOffset(int startOffset) @@ -56,6 +60,9 @@ namespace ICSharpCode.ILSpy.TextView if (this.References == null) return null; foreach (var segment in this.References.FindSegmentsContaining(offset)) { + // skip all non-links + if (!isLink(segment)) + continue; // ensure that hyperlinks don't span several lines (VisualLineElements can't contain line breaks) int endOffset = Math.Min(segment.EndOffset, CurrentContext.VisualLine.LastDocumentLine.EndOffset); // don't create hyperlinks with length 0