mirror of https://github.com/icsharpcode/ILSpy.git
19 changed files with 2056 additions and 64 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,177 @@
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2026 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.IL.Transforms; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.ControlFlow |
||||
{ |
||||
/// <summary>
|
||||
/// Collapses the runtime-async manual await pattern (GetAwaiter / get_IsCompleted /
|
||||
/// AsyncHelpers.UnsafeAwaitAwaiter / GetResult across three blocks) into a single IL
|
||||
/// Await instruction, matching what the state-machine async pipeline emits.
|
||||
/// </summary>
|
||||
static class RuntimeAsyncManualAwaitTransform |
||||
{ |
||||
public static void Run(ILFunction function, ILTransformContext context) |
||||
{ |
||||
bool changed = false; |
||||
foreach (var block in function.Descendants.OfType<Block>().ToArray()) |
||||
{ |
||||
if (DetectRuntimeAsyncManualAwait(block, context)) |
||||
changed = true; |
||||
} |
||||
if (changed) |
||||
{ |
||||
foreach (var c in function.Body.Descendants.OfType<BlockContainer>()) |
||||
c.SortBlocks(deleteUnreachableBlocks: true); |
||||
} |
||||
} |
||||
|
||||
// Block X (head) {
|
||||
// [stloc awaitable(<value>);] (optional: when GetAwaiter takes an address)
|
||||
// stloc awaiter(call GetAwaiter(<expr>))
|
||||
// if (call get_IsCompleted(ldloca awaiter)) br completedBlock
|
||||
// br pauseBlock
|
||||
// }
|
||||
// Block pauseBlock {
|
||||
// call AsyncHelpers.UnsafeAwaitAwaiter[<T>](ldloc awaiter)
|
||||
// br completedBlock
|
||||
// }
|
||||
// Block completedBlock {
|
||||
// call GetResult(ldloca awaiter) (possibly inlined into a containing expression)
|
||||
// ...
|
||||
// }
|
||||
// =>
|
||||
// Block X { ...; br completedBlock }
|
||||
// Block completedBlock { <Await(value)>; ... }
|
||||
static bool DetectRuntimeAsyncManualAwait(Block block, ILTransformContext context) |
||||
{ |
||||
if (block.Instructions.Count < 3) |
||||
return false; |
||||
|
||||
if (!block.Instructions[^3].MatchStLoc(out var awaiterVar, out var awaiterValue)) |
||||
return false; |
||||
if (awaiterValue is not CallInstruction getAwaiterCall) |
||||
return false; |
||||
if (getAwaiterCall.Method.Name != "GetAwaiter" || getAwaiterCall.Arguments.Count != 1) |
||||
return false; |
||||
|
||||
if (block.Instructions[^2] is not IfInstruction ifInst) |
||||
return false; |
||||
var condition = ifInst.Condition; |
||||
if (condition.MatchLogicNot(out var negated)) |
||||
condition = negated; |
||||
if (condition is not CallInstruction isCompletedCall) |
||||
return false; |
||||
if (isCompletedCall.Method.Name != "get_IsCompleted" || isCompletedCall.Arguments.Count != 1) |
||||
return false; |
||||
if (!isCompletedCall.Arguments[0].MatchLdLoca(awaiterVar)) |
||||
return false; |
||||
|
||||
Block completedBlock, pauseBlock; |
||||
if (ifInst.TrueInst.MatchBranch(out var trueBranchTarget) && block.Instructions[^1].MatchBranch(out var falseBranchTarget)) |
||||
{ |
||||
if (negated != null) |
||||
{ |
||||
// if (!IsCompleted) br pauseBlock; br completedBlock — flipped
|
||||
pauseBlock = trueBranchTarget; |
||||
completedBlock = falseBranchTarget; |
||||
} |
||||
else |
||||
{ |
||||
// if (IsCompleted) br completedBlock; br pauseBlock — canonical
|
||||
completedBlock = trueBranchTarget; |
||||
pauseBlock = falseBranchTarget; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
// Block pauseBlock { call AsyncHelpers.UnsafeAwaitAwaiter(ldloc awaiter); br completedBlock }
|
||||
if (pauseBlock.Instructions.Count != 2) |
||||
return false; |
||||
if (pauseBlock.Instructions[0] is not Call pauseCall) |
||||
return false; |
||||
if (!EarlyExpressionTransforms.IsAsyncHelpersMethod(pauseCall.Method, "UnsafeAwaitAwaiter") || pauseCall.Arguments.Count != 1) |
||||
return false; |
||||
if (!pauseCall.Arguments[0].MatchLdLoc(awaiterVar)) |
||||
return false; |
||||
if (!pauseBlock.Instructions[1].MatchBranch(out var pauseTail) || pauseTail != completedBlock) |
||||
return false; |
||||
|
||||
// completedBlock starts with: GetResult(ldloca awaiter), possibly inlined.
|
||||
if (completedBlock.Instructions.Count < 1) |
||||
return false; |
||||
var getResultCall = ILInlining.FindFirstInlinedCall(completedBlock.Instructions[0]); |
||||
if (getResultCall == null) |
||||
return false; |
||||
if (getResultCall.Method.Name != "GetResult" || getResultCall.Arguments.Count != 1) |
||||
return false; |
||||
if (!getResultCall.Arguments[0].MatchLdLoca(awaiterVar)) |
||||
return false; |
||||
|
||||
// Determine the awaitable expression: the original GetAwaiter argument (often `ldloca tmp`)
|
||||
// or, if `tmp` is a fresh temporary set immediately above, the value it was set to.
|
||||
ILInstruction awaitable; |
||||
bool removeTemporaryStore = false; |
||||
if (getAwaiterCall.Arguments[0].MatchLdLoca(out var tmpVar) |
||||
&& block.Instructions.Count >= 4 |
||||
&& block.Instructions[^4].MatchStLoc(tmpVar, out var tmpValue) |
||||
&& tmpVar.StoreCount == 1 |
||||
&& tmpVar.LoadCount == 0 |
||||
&& tmpVar.AddressCount == 1) |
||||
{ |
||||
awaitable = tmpValue; |
||||
removeTemporaryStore = true; |
||||
} |
||||
else |
||||
{ |
||||
awaitable = getAwaiterCall.Arguments[0]; |
||||
} |
||||
|
||||
context.Step("Reduce manual await pattern to Await", block); |
||||
|
||||
// Capture IL ranges before we remove the suspend machinery. The new Await stands in
|
||||
// for the whole pattern (temp store + GetAwaiter + IsCompleted check + UnsafeAwaitAwaiter
|
||||
// + GetResult), and the new `br completedBlock` replaces the original `br pauseBlock`.
|
||||
var oldTailBranch = block.Instructions[^1]; |
||||
var awaitInst = new Await(awaitable.Clone()).WithILRange(getResultCall); |
||||
if (removeTemporaryStore) |
||||
awaitInst.AddILRange(block.Instructions[^4]); |
||||
awaitInst.AddILRange(block.Instructions[^3]); |
||||
awaitInst.AddILRange(block.Instructions[^2]); |
||||
foreach (var inst in pauseBlock.Instructions) |
||||
awaitInst.AddILRange(inst); |
||||
awaitInst.GetAwaiterMethod = getAwaiterCall.Method; |
||||
awaitInst.GetResultMethod = getResultCall.Method; |
||||
|
||||
// Remove the trailing 3 (or 4) instructions of the head block; replace with `br completedBlock`.
|
||||
int removeCount = removeTemporaryStore ? 4 : 3; |
||||
block.Instructions.RemoveRange(block.Instructions.Count - removeCount, removeCount); |
||||
block.Instructions.Add(new Branch(completedBlock).WithILRange(oldTailBranch)); |
||||
|
||||
getResultCall.ReplaceWith(awaitInst); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue