From 03e3a17f1b05b65410464a5586be4445b2dca172 Mon Sep 17 00:00:00 2001 From: Christoph Wille <christoph.wille@gmail.com> Date: Sun, 17 Mar 2024 12:22:11 +0100 Subject: [PATCH 1/3] Update to Roslyn 4.9.2 --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2492143b7c..dc161bfe5a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,8 +15,8 @@ <PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" /> <PackageVersion Include="K4os.Compression.LZ4" Version="1.3.6" /> <PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.0" /> - <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" /> - <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.8.0" /> + <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> + <PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" /> <PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" /> <PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" /> <PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" /> From 9ba47db69b00bbb4e7d101ad8d65be8bb7143e04 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 29 Mar 2024 21:14:18 +0100 Subject: [PATCH 2/3] Add new a.GetValueOrDefault(b) -> a ?? b transform for side-effect-free default values. --- .../IL/Transforms/ExpressionTransforms.cs | 12 ++++++++++++ .../IL/Transforms/NullableLiftingTransform.cs | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 7f494cad6b..5385bbaae6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -274,6 +274,18 @@ void VisitLogicNot(Comp inst, ILInstruction arg) protected internal override void VisitCall(Call inst) { + if (NullableLiftingTransform.MatchGetValueOrDefault(inst, out var nullableValue, out var fallback) + && SemanticHelper.IsPure(fallback.Flags)) + { + context.Step("call Nullable{T}.GetValueOrDefault(a, b) -> a ?? b", inst); + var ldObj = new LdObj(nullableValue, inst.Method.DeclaringType); + var replacement = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, ldObj, fallback) { + UnderlyingResultType = fallback.ResultType + }; + inst.ReplaceWith(replacement.WithILRange(inst)); + replacement.AcceptVisitor(this); + return; + } base.VisitCall(inst); TransformAssignment.HandleCompoundAssign(inst, context); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index 44f73e3e47..4e297b00d9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -16,7 +16,6 @@ // 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.Diagnostics; using System.Linq; @@ -1047,6 +1046,24 @@ internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstructio return true; } + /// <summary> + /// Matches 'call nullableValue.GetValueOrDefault(fallback)' + /// </summary> + internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction nullableValue, out ILInstruction fallback) + { + nullableValue = null; + fallback = null; + if (!(inst is Call call)) + return false; + if (call.Method.Name != "GetValueOrDefault" || call.Arguments.Count != 2) + return false; + if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT) + return false; + nullableValue = call.Arguments[0]; + fallback = call.Arguments[1]; + return true; + } + /// <summary> /// Matches 'call Nullable{T}.GetValueOrDefault(ldloca v)' /// </summary> From 969e3e546aa603e80c0e8162c0d8a44c1c2f0eb5 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer <siegfriedpammer@gmail.com> Date: Fri, 29 Mar 2024 21:17:51 +0100 Subject: [PATCH 3/3] Add support for switch on (ReadOnly)Span<char> using a compiler-generated hash function. --- .../IL/ControlFlow/SwitchDetection.cs | 4 +- .../IL/Transforms/SwitchOnStringTransform.cs | 39 ++++++++++++++----- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs index 3fb1734daa..dc6bd309c9 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs @@ -16,12 +16,10 @@ // 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.Diagnostics; using System.Linq; -using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.TypeSystem; @@ -361,7 +359,7 @@ private bool UseCSharpSwitch(out KeyValuePair<LongSet, ILInstruction> defaultSec private bool MatchRoslynSwitchOnString() { var insns = analysis.RootBlock.Instructions; - return insns.Count >= 3 && SwitchOnStringTransform.MatchComputeStringHashCall(insns[insns.Count - 3], analysis.SwitchVariable, out var switchLdLoc); + return insns.Count >= 3 && SwitchOnStringTransform.MatchComputeStringOrReadOnlySpanHashCall(insns[insns.Count - 3], analysis.SwitchVariable, out _); } /// <summary> diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index d59041381a..e254d243b6 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -294,6 +294,16 @@ bool AddSwitchSection(string value, ILInstruction inst) keepAssignmentBefore = true; switchValue = new LdLoc(switchValueVar); } + if (!switchValueVar.Type.IsKnownType(KnownTypeCode.String)) + { + if (!context.Settings.SwitchOnReadOnlySpanChar) + return false; + if (!switchValueVar.Type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) + && !switchValueVar.Type.IsKnownType(KnownTypeCode.SpanOfT)) + { + return false; + } + } // if instruction must be followed by a branch to the next case if (!nextCaseJump.MatchBranch(out Block currentCaseBlock)) return false; @@ -335,7 +345,7 @@ bool AddSwitchSection(string value, ILInstruction inst) int offset = firstBlock == null ? 1 : 0; var sections = new List<SwitchSection>(values.Skip(offset).SelectWithIndex((index, s) => new SwitchSection { Labels = new LongSet(index), Body = s.Item2 is Block b ? new Branch(b) : s.Item2.Clone() })); sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = currentCaseBlock != null ? (ILInstruction)new Branch(currentCaseBlock) : new Leave((BlockContainer)nextCaseBlock) }); - var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), context.TypeSystem.FindType(KnownTypeCode.String)); + var stringToInt = new StringToInt(switchValue, values.Skip(offset).Select(item => item.Item1).ToArray(), switchValueVar.Type); var inst = new SwitchInstruction(stringToInt); inst.Sections.AddRange(sections); if (removeExtraLoad) @@ -1008,7 +1018,7 @@ bool MatchRoslynSwitchOnString(InstructionCollection<ILInstruction> instructions if (!(switchBlockInstructionsOffset + 1 < switchBlockInstructions.Count && switchBlockInstructions[switchBlockInstructionsOffset + 1] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) - && MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], + && MatchComputeStringOrReadOnlySpanHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad))) { return false; @@ -1643,19 +1653,28 @@ bool MatchStringLengthCall(ILInstruction inst, ILVariable switchValueVar) } /// <summary> - /// Matches 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))' + /// Matches + /// 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))' + /// - or - + /// 'stloc(targetVar, call ComputeSpanHash(ldloc switchValue))' + /// - or - + /// 'stloc(targetVar, call ComputeReadOnlySpanHash(ldloc switchValue))' /// </summary> - internal static bool MatchComputeStringHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue) + internal static bool MatchComputeStringOrReadOnlySpanHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue) { switchValue = null; if (!inst.MatchStLoc(targetVar, out var value)) return false; - if (!(value is Call c && c.Arguments.Count == 1 && c.Method.Name == "ComputeStringHash" && c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass())) - return false; - if (!(c.Arguments[0] is LdLoc)) - return false; - switchValue = (LdLoc)c.Arguments[0]; - return true; + if (value is Call c && c.Arguments.Count == 1 + && c.Method.Name is "ComputeStringHash" or "ComputeSpanHash" or "ComputeReadOnlySpanHash" + && c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + { + if (c.Arguments[0] is not LdLoc ldloc) + return false; + switchValue = ldloc; + return true; + } + return false; } /// <summary>