Skip to content

Commit aa7fa1a

Browse files
WIP fix
1 parent 440f148 commit aa7fa1a

File tree

7 files changed

+169
-20
lines changed

7 files changed

+169
-20
lines changed

ICSharpCode.Decompiler/IL/Instructions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#nullable enable
2020

2121
using System;
22-
using System.Diagnostics;
2322
using System.Diagnostics.CodeAnalysis;
2423
using System.Linq;
2524

@@ -4152,6 +4151,7 @@ public IType Type {
41524151
get { return type; }
41534152
set { type = value; InvalidateFlags(); }
41544153
}
4154+
public bool ImplicitDeference;
41554155
public override StackType ResultType { get { return StackType.Ref; } }
41564156
protected override InstructionFlags ComputeFlags()
41574157
{
@@ -4165,6 +4165,8 @@ public override InstructionFlags DirectFlags {
41654165
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
41664166
{
41674167
WriteILRange(output, options);
4168+
if (ImplicitDeference)
4169+
output.Write("implicitdeference.");
41684170
output.Write(OpCode);
41694171
output.Write(' ');
41704172
type.WriteTo(output);
@@ -4187,7 +4189,7 @@ public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
41874189
protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match)
41884190
{
41894191
var o = other as LdObjIfRef;
4190-
return o != null && this.target.PerformMatch(o.target, ref match) && type.Equals(o.type);
4192+
return o != null && this.target.PerformMatch(o.target, ref match) && type.Equals(o.type) && this.ImplicitDeference == o.ImplicitDeference;
41914193
}
41924194
internal override void CheckInvariant(ILPhase phase)
41934195
{

ICSharpCode.Decompiler/IL/Instructions.tt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@
256256
CustomClassName("LdObj"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
257257
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")),
258258
new OpCode("ldobj.if.ref", "If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.<para>This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).</para>",
259-
CustomClassName("LdObjIfRef"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess,
259+
CustomClassName("LdObjIfRef"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess, BoolFlag("ImplicitDeference"),
260260
MayThrow, ResultType("Ref")),
261261
new OpCode("stobj", "Indirect store (store to ref/pointer)." + Environment.NewLine
262262
+ "Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())",

ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,10 @@ private static FindResult NoContinue(FindResult findResult)
861861
/// </summary>
862862
static bool IsSafeForInlineOver(ILInstruction expr, ILInstruction expressionBeingMoved)
863863
{
864+
if (expr is LdObjIfRef { ImplicitDeference: true })
865+
{
866+
return true;
867+
}
864868
return SemanticHelper.MayReorder(expressionBeingMoved, expr);
865869
}
866870

ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ bool IsValidAccessChain(ILVariable testedVar, Mode mode, ILInstruction inst, out
264264
inst = target;
265265
}
266266
}
267+
else if (inst.MatchLdObjIfRef(out target, out _))
268+
{
269+
inst = target;
270+
}
267271
else if (inst is CallInstruction call && call.OpCode != OpCode.NewObj)
268272
{
269273
if (call.Arguments.Count == 0)
@@ -362,7 +366,7 @@ bool IsValidEndOfChain()
362366
&& arg.MatchLdLoc(testedVar);
363367
case Mode.UnconstrainedType:
364368
// unconstrained generic type (expect: ldloc(testedVar))
365-
return inst.MatchLdLoc(testedVar);
369+
return inst.MatchLdLoc(testedVar) || (inst.MatchLdObjIfRef(out var testedVarLoad, out _) && testedVarLoad.MatchLdLoc(testedVar));
366370
default:
367371
throw new ArgumentOutOfRangeException(nameof(mode));
368372
}

ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs

Lines changed: 138 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,6 @@
2323

2424
namespace ICSharpCode.Decompiler.IL.Transforms
2525
{
26-
/// <summary>
27-
/// Block IL_0018 (incoming: *) {
28-
/// stloc s(ldc.i4 1)
29-
/// br IL_0019
30-
/// }
31-
///
32-
/// Block IL_0019 (incoming: > 1) {
33-
/// if (logic.not(ldloc s)) br IL_0027
34-
/// br IL_001d
35-
/// }
36-
///
37-
/// replace br IL_0019 with br IL_0027
38-
/// </summary>
3926
class RemoveInfeasiblePathTransform : IILTransform
4027
{
4128
void IILTransform.Run(ILFunction function, ILTransformContext context)
@@ -45,7 +32,8 @@ void IILTransform.Run(ILFunction function, ILTransformContext context)
4532
bool changed = false;
4633
foreach (var block in container.Blocks)
4734
{
48-
changed |= DoTransform(block, context);
35+
changed |= RemoveInfeasiblePath(block, context);
36+
changed |= RemoveUnconstrainedGenericReferenceTypeCheck(block, context);
4937
}
5038

5139
if (changed)
@@ -55,8 +43,20 @@ void IILTransform.Run(ILFunction function, ILTransformContext context)
5543
}
5644
}
5745

58-
59-
private bool DoTransform(Block block, ILTransformContext context)
46+
/// <summary>
47+
/// Block IL_0018 (incoming: *) {
48+
/// stloc s(ldc.i4 1)
49+
/// br IL_0019
50+
/// }
51+
///
52+
/// Block IL_0019 (incoming: > 1) {
53+
/// if (logic.not(ldloc s)) br IL_0027
54+
/// br IL_001d
55+
/// }
56+
///
57+
/// replace br IL_0019 with br IL_0027
58+
/// </summary>
59+
private bool RemoveInfeasiblePath(Block block, ILTransformContext context)
6060
{
6161
if (!MatchBlock1(block, out var s, out int value, out var br))
6262
return false;
@@ -112,5 +112,127 @@ bool MatchBlock2(Block block, ILVariable s, int constantValue, [NotNullWhen(true
112112
exitInst = constantValue != 0 ? trueInst : falseInst;
113113
return exitInst is Branch or Leave { Value: Nop };
114114
}
115+
116+
/// <summary>
117+
/// Block entryPoint (incoming: _) {
118+
/// [...]
119+
/// stloc S_0(...)
120+
/// stobj ``0(ldloca V_0, default.value ``0)
121+
/// if (comp.o(box ``0(ldloc V_0) != ldnull)) br invocationBlock
122+
/// br dereferenceBlock
123+
/// }
124+
///
125+
/// Block dereferenceBlock (incoming: 1) {
126+
/// stloc V_0(ldobj ``0(ldloc S_0))
127+
/// stloc S_0(ldloca V_0)
128+
/// br invocationBlock
129+
/// }
130+
///
131+
/// Block invocationBlock (incoming: 2) {
132+
/// [...]
133+
/// ... (constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc S_0), ...))
134+
/// }
135+
/// </summary>
136+
private bool RemoveUnconstrainedGenericReferenceTypeCheck(Block entryPoint, ILTransformContext context)
137+
{
138+
// if (comp.o(box ``0(ldloc temporary) != ldnull)) br invocationBlock
139+
// br dereferenceBlock
140+
if (!entryPoint.MatchIfAtEndOfBlock(out var condition, out var invocationBlockBranch, out var dereferenceBlockBranch))
141+
{
142+
return false;
143+
}
144+
if (!condition.MatchCompNotEqualsNull(out var arg))
145+
{
146+
return false;
147+
}
148+
if (!arg.MatchBox(out arg, out var type))
149+
{
150+
return false;
151+
}
152+
if (!arg.MatchLdLoc(out var temp) || temp.StoreCount != 2 || temp.AddressCount != 2)
153+
{
154+
return false;
155+
}
156+
// stobj ``0(ldloca V_0, default.value ``0)
157+
var store = entryPoint.Instructions.ElementAtOrDefault(entryPoint.Instructions.Count - 3);
158+
if (store == null || !store.MatchStObj(out var target, out var value, out var storeType))
159+
{
160+
return false;
161+
}
162+
if (!target.MatchLdLoca(temp) || !value.MatchDefaultValue(out var defaultValueType))
163+
{
164+
return false;
165+
}
166+
if (!defaultValueType.Equals(storeType) || !storeType.Equals(type))
167+
{
168+
return false;
169+
}
170+
// stloc S_0(...)
171+
store = entryPoint.Instructions.ElementAtOrDefault(entryPoint.Instructions.Count - 4);
172+
if (store == null || !store.MatchStLoc(out var stackSlot, out var thisValue))
173+
{
174+
return false;
175+
}
176+
// check dereferenceBlock
177+
if (!dereferenceBlockBranch.MatchBranch(out var dereferenceBlock) || !invocationBlockBranch.MatchBranch(out var invocationBlock))
178+
{
179+
return false;
180+
}
181+
if (invocationBlock.IncomingEdgeCount != 2)
182+
{
183+
return false;
184+
}
185+
if (dereferenceBlock.IncomingEdgeCount != 1)
186+
{
187+
return false;
188+
}
189+
190+
// stloc V_0(ldobj ``0(ldloc S_0))
191+
// stloc S_0(ldloca V_0)
192+
// br invocationBlock
193+
if (dereferenceBlock.Instructions is not [StLoc deref, StLoc addressLoad, Branch br])
194+
{
195+
return false;
196+
}
197+
if (deref.Variable != temp || addressLoad.Variable != stackSlot)
198+
{
199+
return false;
200+
}
201+
if (!deref.Value.MatchLdObj(out var stackSlotTarget, out var loadType))
202+
{
203+
return false;
204+
}
205+
if (!stackSlotTarget.MatchLdLoc(stackSlot) || !loadType.Equals(type))
206+
{
207+
return false;
208+
}
209+
if (!addressLoad.Value.MatchLdLoca(temp))
210+
{
211+
return false;
212+
}
213+
if (br?.TargetBlock != invocationBlock)
214+
{
215+
return false;
216+
}
217+
// ... (constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc S_0), ...))
218+
if (stackSlot.StoreCount != 2 || stackSlot.LoadCount != 2 || stackSlot.AddressCount != 0)
219+
{
220+
return false;
221+
}
222+
var callTarget = stackSlot.LoadInstructions.SingleOrDefault(l => stackSlotTarget != l);
223+
if (callTarget?.Parent is not LdObjIfRef { Parent: CallVirt call } ldobjIfRef)
224+
{
225+
return false;
226+
}
227+
if (call.Arguments.Count == 0 || call.Arguments[0] != ldobjIfRef || !storeType.Equals(call.ConstrainedTo))
228+
{
229+
return false;
230+
}
231+
context.Step("RemoveUnconstrainedGenericReferenceTypeCheck", store);
232+
ldobjIfRef.ImplicitDeference = true;
233+
entryPoint.Instructions.RemoveRange(entryPoint.Instructions.Count - 3, 3);
234+
entryPoint.Instructions.Add(invocationBlockBranch);
235+
return true;
236+
}
115237
}
116238
}

ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,22 @@ public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruc
371371
}
372372
goto default;
373373
}
374+
case LdObjIfRef ldobj:
375+
{
376+
if (ldobj.Target is LdFlda ldflda && (kind != AccessPathKind.Setter || !ldflda.Field.IsReadOnly))
377+
{
378+
path.Insert(0, new AccessPathElement(ldobj.OpCode, ldflda.Field));
379+
inst = ldflda.Target;
380+
break;
381+
}
382+
if (ldobj.Target is LdLoca ldloca)
383+
{
384+
target = ldloca.Variable;
385+
inst = null;
386+
break;
387+
}
388+
goto default;
389+
}
374390
case StObj stobj:
375391
{
376392
if (stobj.Target is LdFlda ldflda)

ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isRefere
398398
return false;
399399
return target.MatchLdLocRef(objVar)
400400
|| (boxedValue && target.MatchLdLoc(objVar))
401+
|| (target.MatchLdObjIfRef(out var objVarLoada, out _) && objVarLoada.MatchLdLoca(objVar))
401402
|| (usingNull && disposeCall.Arguments[0].MatchLdNull())
402403
|| (isReference && checkInst is NullableRewrap
403404
&& target.MatchIsInst(out var arg, out var type2)

0 commit comments

Comments
 (0)