Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public static void Test()
}
}

public class Issue3827Node
{
public Issue3827Node next;

public int value;
}

public delegate ref T RefFunc<T>();
public delegate ref readonly T ReadOnlyRefFunc<T>();
public delegate ref TReturn RefFunc<T1, TReturn>(T1 param1);
Expand Down Expand Up @@ -159,6 +166,28 @@ public void RefReadonlyCallVirt(RefLocalsAndReturns provider)

private static int DefaultInt = 0;

public static Issue3827Node RefLocalUsedAfterForLoop(Issue3827Node[] buckets, int hash, Issue3827Node newNode)
{
// The ref local is used after the loop, so its declaration is hoisted in front of the loop.
// The loop is kept as a while-loop (rather than converted to a headless for-loop) so the
// ref-assignment stays on the declaration -- a ref local cannot be declared without an initializer.
ref Issue3827Node reference = ref buckets[hash & 3];
while (reference != null)
{
if (reference.value == hash)
{
reference = newNode;
break;
}
reference = ref reference.next;
}
if (reference == null)
{
reference = newNode;
}
return reference;
}

public static ref T GetRef<T>()
{
throw new NotImplementedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ public override AstNode VisitTryCatchStatement(TryCatchStatement tryCatchStateme
if (variable != m3.Get<IdentifierExpression>("ident").Single().GetILVariable())
return null;
WhileStatement loop = (WhileStatement)next;
// Cannot convert to for loop, if the iteration variable is a ref local used after the loop: its
// declaration is hoisted in front, leaving a headless `for (; cond; v = ref ...)` whose only
// initialization is the for-initializer ref-assignment -- which can't be split from a ref local
// (CS8174). Keeping it a while-loop matches the source and keeps the initializer on the decl.
if (variable != null && variable.Type.IsByRefLike && IsVariableUsedAfter(loop, variable))
return null;
// Cannot convert to for loop, if any variable that is used in the "iterator" part of the pattern,
// will be declared in the body of the while-loop.
var iteratorStatement = m3.Get<Statement>("iterator").Single();
Expand Down Expand Up @@ -244,6 +250,16 @@ bool ForStatementUsesVariable(ForStatement statement, IL.ILVariable? variable)
return false;
}

bool IsVariableUsedAfter(Statement loop, IL.ILVariable variable)
{
for (AstNode? sibling = loop.NextSibling; sibling != null; sibling = sibling.NextSibling)
{
if (sibling.DescendantsAndSelf.OfType<IdentifierExpression>().Any(ie => ie.GetILVariable() == variable))
return true;
}
return false;
}

bool IteratorVariablesDeclaredInsideLoopBody(Statement iteratorStatement)
{
foreach (var id in iteratorStatement.DescendantsAndSelf.OfType<IdentifierExpression>())
Expand Down
23 changes: 23 additions & 0 deletions ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ bool MatchForLoop(BlockContainer loop, IfInstruction whileCondition, Block while
// - increment block
if (incrementBlock.Instructions.Count <= 1 || loop.Blocks.Count < 3)
return false;
if (StoresRefLocalUsedAfterLoop(loop, incrementBlock))
return false;
context.Step("Transform to for loop: " + loop.EntryPoint.Label, loop);
// move the block to the end of the loop:
loop.Blocks.MoveElementToEnd(incrementBlock);
Expand Down Expand Up @@ -467,6 +469,27 @@ bool MatchForLoop(BlockContainer loop, IfInstruction whileCondition, Block while
return true;
}

static bool StoresRefLocalUsedAfterLoop(BlockContainer loop, Block incrementBlock)
{
foreach (var store in incrementBlock.Instructions.SkipLast(1).SelectMany(inst => inst.Descendants).OfType<StLoc>())
{
var variable = store.Variable;
if (variable.Type.IsByRefLike && IsVariableUsedAfterLoop(loop, variable))
return true;
}
return false;
}

static bool IsVariableUsedAfterLoop(BlockContainer loop, ILVariable variable)
{
foreach (var use in variable.LoadInstructions.Concat<ILInstruction>(variable.AddressInstructions).Concat(variable.StoreInstructions.Cast<ILInstruction>()))
{
if (loop.GetCommonParent(use) is Block { Kind: BlockKind.ControlFlow } && loop.IsBefore(use))
return true;
}
return false;
}

bool IsAssignment(ILInstruction inst)
{
if (inst is StLoc)
Expand Down
Loading