From 0ab608f9814015c166c23067165cd0e91507f0bb Mon Sep 17 00:00:00 2001 From: Marcus Mikelic <29722978+samoriental@users.noreply.github.com> Date: Mon, 7 Sep 2026 22:30:05 -0400 Subject: [PATCH] [Decompiler] gpt-slop copy --- .../CSharp/ExpressionBuilder.cs | 5 + .../CSharp/TranslatedExpression.cs | 11 ++ .../FlowAnalysis/DataFlowVisitor.cs | 9 ++ ICSharpCode.Decompiler/IL/Instructions.cs | 123 ++++++++++++++++++ ICSharpCode.Decompiler/IL/Instructions.tt | 3 + .../CachedDelegateInitialization.cs | 27 ++-- global.json | 2 +- 7 files changed, 166 insertions(+), 14 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 58546162b..67867375f 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -558,6 +558,11 @@ namespace ICSharpCode.Decompiler.CSharp return new CallBuilder(this, typeSystem, settings).Build(inst, context.TypeHint); } + protected internal override TranslatedExpression VisitCachedDelegate(CachedDelegate inst, TranslationContext context) + { + return Translate(inst.Argument, context.TypeHint).WithILInstruction(inst); + } + protected internal override TranslatedExpression VisitLdVirtDelegate(LdVirtDelegate inst, TranslationContext context) { return new CallBuilder(this, typeSystem, settings).Build(inst); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index d682da2eb..58c5018b2 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -27,6 +27,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.Decompiler.Util; #nullable enable @@ -270,6 +271,16 @@ namespace ICSharpCode.Decompiler.CSharp else if (Expression is ObjectCreateExpression oce && conversion.Conversion.IsMethodGroupConversion && oce.Arguments.Count == 1 && expressionBuilder.settings.UseImplicitMethodGroupConversion) { + // C# 11 caches static method groups. Keep explicit construction when the IL creates a fresh delegate. + if (conversion.Conversion.Method.IsStatic + && conversion.Conversion.Method is not LocalFunctionMethod { IsStaticLocalFunction: false } + && conversion.Conversion.Method.Parameters.Count == type.GetDelegateInvokeMethod()?.Parameters.Count + && expressionBuilder.settings.GetMinimumRequiredVersion() >= LanguageVersion.CSharp11_0 + && expressionBuilder.currentFunction.Kind != ILFunctionKind.ExpressionTree + && !ILInstructions.Any(i => i is CachedDelegate)) + { + return this; + } return this.UnwrapChild(oce.Arguments.Single()); } break; diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs index 82d9560b1..e0bcba5c5 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs @@ -754,6 +754,15 @@ namespace ICSharpCode.Decompiler.FlowAnalysis HandleBinaryWithOptionalEvaluation(inst, inst.ValueInst, inst.FallbackInst); } + protected internal override void VisitCachedDelegate(CachedDelegate inst) + { + DebugStartPoint(inst); + State cachedState = state.Clone(); + inst.Argument.AcceptVisitor(this); + state.JoinWith(cachedState); + DebugEndPoint(inst); + } + protected internal override void VisitDynamicLogicOperatorInstruction(DynamicLogicOperatorInstruction inst) { HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right); diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 18072653c..604c58c95 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -177,6 +177,8 @@ namespace ICSharpCode.Decompiler.IL UnboxAny, /// Creates an object instance and calls the constructor. NewObj, + /// Reuses a cached delegate, evaluating the argument only when the cache is empty. + CachedDelegate, /// Creates an array instance. NewArr, /// Returns the default value for a type. @@ -4580,6 +4582,103 @@ namespace ICSharpCode.Decompiler.IL } } namespace ICSharpCode.Decompiler.IL +{ + /// Reuses a cached delegate, evaluating the argument only when the cache is empty. + public sealed partial class CachedDelegate : ILInstruction + { + public CachedDelegate(ILInstruction argument) : base(OpCode.CachedDelegate) + { + this.Argument = argument; + } + public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument"); + ILInstruction argument = null!; + public ILInstruction Argument { + get { return this.argument; } + set { + ValidateChild(value); + SetChildInstruction(ref this.argument, value, 0); + } + } + protected sealed override int GetChildCount() + { + return 1; + } + protected sealed override ILInstruction GetChild(int index) + { + switch (index) + { + case 0: + return this.argument; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override void SetChild(int index, ILInstruction value) + { + switch (index) + { + case 0: + this.Argument = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override SlotInfo GetChildSlot(int index) + { + switch (index) + { + case 0: + return ArgumentSlot; + default: + throw new IndexOutOfRangeException(); + } + } + public sealed override ILInstruction Clone() + { + var clone = (CachedDelegate)ShallowClone(); + clone.Argument = this.argument.Clone(); + return clone; + } + public override StackType ResultType => Argument.ResultType; + public override IType InferType(ICompilation compilation) => Argument.InferType(compilation); + protected override InstructionFlags ComputeFlags() + { + return argument.Flags | InstructionFlags.ControlFlow; + } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } + } + protected override void WriteToCore(ITextOutput output, ILAstWritingOptions options) + { + WriteILRange(output, options); + output.Write(OpCode); + output.Write('('); + this.argument.WriteTo(output, options); + output.Write(')'); + } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitCachedDelegate(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitCachedDelegate(this); + } + public override T AcceptVisitor(ILVisitor visitor, C context) + { + return visitor.VisitCachedDelegate(this, context); + } + protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match) + { + var o = other as CachedDelegate; + return o != null && this.argument.PerformMatch(o.argument, ref match); + } + } +} +namespace ICSharpCode.Decompiler.IL { /// Creates an array instance. public sealed partial class NewArr : ILInstruction @@ -7412,6 +7511,10 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitCachedDelegate(CachedDelegate inst) + { + Default(inst); + } protected internal virtual void VisitNewArr(NewArr inst) { Default(inst); @@ -7822,6 +7925,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitCachedDelegate(CachedDelegate inst) + { + return Default(inst); + } protected internal virtual T VisitNewArr(NewArr inst) { return Default(inst); @@ -8232,6 +8339,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst, context); } + protected internal virtual T VisitCachedDelegate(CachedDelegate inst, C context) + { + return Default(inst, context); + } protected internal virtual T VisitNewArr(NewArr inst, C context) { return Default(inst, context); @@ -8433,6 +8544,7 @@ namespace ICSharpCode.Decompiler.IL "unbox", "unbox.any", "newobj", + "cached.delegate", "newarr", "default.value", "throw", @@ -9003,6 +9115,17 @@ namespace ICSharpCode.Decompiler.IL type = default(IType); return false; } + public bool MatchCachedDelegate([NotNullWhen(true)] out ILInstruction? argument) + { + var inst = this as CachedDelegate; + if (inst != null) + { + argument = inst.Argument; + return true; + } + argument = default(ILInstruction); + return false; + } public bool MatchNewArr([NotNullWhen(true)] out IType? type) { var inst = this as NewArr; diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 8a20bff22..bfac4f2da 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -287,6 +287,9 @@ Unary, HasTypeOperand, MemoryAccess, MayThrow, ResultType("this.type")), new OpCode("newobj", "Creates an object instance and calls the constructor.", CustomClassName("NewObj"), Call, ResultType("Method.DeclaringType")), + new OpCode("cached.delegate", "Reuses a cached delegate, evaluating the argument only when the cache is empty.", + CustomChildren(new [] { new ChildInfo("argument") }), ControlFlow, + ResultType("Argument.InferType(compilation)", "Argument.ResultType")), new OpCode("newarr", "Creates an array instance.", CustomClassName("NewArr"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("indices") { IsCollection = true } }, true), diff --git a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs index e67f31123..93941cdbe 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs @@ -79,7 +79,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// } /// ... one usage of CachedAnonMethodDelegate ... /// => - /// ... one usage of DelegateConstruction ... + /// ... one usage of cached.delegate(DelegateConstruction) ... /// bool CachedDelegateInitializationWithField(IfInstruction inst) { @@ -101,7 +101,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (usages.Length != 1) return false; context.Step("CachedDelegateInitializationWithField", inst); - usages[0].ReplaceWith(value); + usages[0].ReplaceWith(new CachedDelegate(value)); context.EndStep(value); return true; } @@ -111,7 +111,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc v(DelegateConstruction) /// } /// => - /// stloc v(DelegateConstruction) + /// stloc v(cached.delegate(DelegateConstruction)) /// bool CachedDelegateInitializationWithLocal(IfInstruction inst) { @@ -141,6 +141,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; context.Step("CachedDelegateInitializationWithLocal", inst); ((Block)otherStore.Parent).Instructions.Remove(otherStore); + ((StLoc)storeInst).Value = new CachedDelegate(value); inst.ReplaceWith(storeInst); context.EndStep(storeInst); return true; @@ -152,7 +153,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc s(stobj(ldsflda(CachedAnonMethodDelegate), DelegateConstruction)) /// } /// => - /// stloc s(DelegateConstruction) + /// stloc s(cached.delegate(DelegateConstruction)) /// bool CachedDelegateInitializationRoslynInStaticWithLocal(IfInstruction inst) { @@ -174,7 +175,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true)) return false; context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst); - storeBeforeIf.Value = stobj.Value; + storeBeforeIf.Value = new CachedDelegate(stobj.Value); return true; } @@ -184,7 +185,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc s(stobj(ldflda(CachedAnonMethodDelegate), DelegateConstruction)) /// } /// => - /// stloc s(DelegateConstruction) + /// stloc s(cached.delegate(DelegateConstruction)) /// bool CachedDelegateInitializationRoslynWithLocal(IfInstruction inst) { @@ -206,7 +207,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true)) return false; context.Step("CachedDelegateInitializationRoslynWithLocal", inst); - storeBeforeIf.Value = stobj.Value; + storeBeforeIf.Value = new CachedDelegate(stobj.Value); return true; } @@ -217,7 +218,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc s(ldobj System.Action(ldsflda $I4-1)) /// } /// => - /// stloc s(DelegateConstruction) + /// stloc s(cached.delegate(DelegateConstruction)) /// bool CachedDelegateInitializationVB(IfInstruction inst) { @@ -249,7 +250,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) return false; context.Step("CachedDelegateInitializationVB", inst); - var stloc = new StLoc(s, delegateConstruction); + var stloc = new StLoc(s, new CachedDelegate(delegateConstruction)); inst.ReplaceWith(stloc); context.EndStep(stloc); return true; @@ -261,7 +262,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// } /// leave IL_0005 (stsfld CachedAnonMethodDelegate(DelegateConstruction)) /// => - /// leave IL_0005 (DelegateConstruction) + /// leave IL_0005 (cached.delegate(DelegateConstruction)) /// bool CachedDelegateInitializationVBWithReturn(IfInstruction inst) { @@ -279,7 +280,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) return false; context.Step("CachedDelegateInitializationVBWithReturn", inst); - leaveAfterIf.Value = delegateConstruction; + leaveAfterIf.Value = new CachedDelegate(delegateConstruction); return true; } @@ -290,7 +291,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// stloc s(stobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure), DelegateConstruction)) /// } /// => - /// stloc s(DelegateConstruction) + /// stloc s(cached.delegate(DelegateConstruction)) /// bool CachedDelegateInitializationVBWithClosure(IfInstruction inst) { @@ -322,7 +323,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) return false; context.Step("CachedDelegateInitializationVBWithClosure", inst); - var stloc = new StLoc(s, delegateConstruction); + var stloc = new StLoc(s, new CachedDelegate(delegateConstruction)); inst.ReplaceWith(stloc); context.EndStep(stloc); return true; diff --git a/global.json b/global.json index 115ca7301..58a3053ca 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "11.0.0", + "version": "11.0.100-preview.7.26381.103", "rollForward": "major", "allowPrerelease": true },