Skip to content
Draft
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
10 changes: 9 additions & 1 deletion ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public enum CompilerOptions
CheckForOverflowUnderflow = 0x20000,
ProcessXmlDoc = 0x40000,
UseRoslyn4_14_0 = 0x80000,
EnableRuntimeAsync = 0x100000,
UseMcsMask = UseMcs2_6_4 | UseMcs5_23,
UseRoslynMask = UseRoslyn1_3_2 | UseRoslyn2_10_0 | UseRoslyn3_11_0 | UseRoslyn4_14_0 | UseRoslynLatest
}
Expand Down Expand Up @@ -605,13 +606,18 @@ public static async Task<CompilerResults> CompileCSharp(string sourceFileName, C
if (roslynVersion != "legacy")
{
otherOptions += "/shared ";
if (!targetNet40 && Version.Parse(RoslynToolset.SanitizeVersion(roslynVersion)).Major > 2)
var version = Version.Parse(RoslynToolset.SanitizeVersion(roslynVersion));
if (!targetNet40 && version.Major > 2)
{
if (flags.HasFlag(CompilerOptions.NullableEnable))
otherOptions += "/nullable+ ";
else
otherOptions += "/nullable- ";
}
if (!targetNet40 && roslynVersion == roslynLatestVersion && flags.HasFlag(CompilerOptions.EnableRuntimeAsync))
{
otherOptions += "/features:runtime-async=on ";
Comment thread
siegfriedpammer marked this conversation as resolved.
}
}

if (flags.HasFlag(CompilerOptions.Library))
Expand Down Expand Up @@ -842,6 +848,8 @@ internal static string GetSuffix(CompilerOptions cscOptions)
suffix += ".mcs2";
if ((cscOptions & CompilerOptions.UseMcs5_23) != 0)
suffix += ".mcs5";
if ((cscOptions & CompilerOptions.EnableRuntimeAsync) != 0)
suffix += ".runtimeasync";
return suffix;
}

Expand Down
40 changes: 40 additions & 0 deletions ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,46 @@ public async Task CustomTaskType([ValueSource(nameof(roslyn2OrNewerOptions))] Co
await RunForLibrary(cscOptions: cscOptions);
}

[Test]
public async Task RuntimeAsync([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary("Async", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
}

[Test]
public async Task RuntimeAsyncForeach([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary("AsyncForeach", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
}

[Test]
public async Task RuntimeAsyncMain([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await Run("AsyncMain", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
}

[Test]
public async Task RuntimeAsyncStreams([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary("AsyncStreams", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
}

[Test]
public async Task RuntimeAsyncUsing([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(
"AsyncUsing",
cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview,
configureDecompiler: settings => { settings.UseEnhancedUsing = false; }
);
}

[Test]
public async Task RuntimeAsyncCustomTaskType([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary("CustomTaskType", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview);
}

[Test]
public async Task NullableRefTypes([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
{
Expand Down
24 changes: 24 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public async Task SimpleVoidTaskMethod()
Console.WriteLine("After");
}

[MethodImpl(MethodImplOptions.NoInlining)]
public async Task NoInliningTaskMethod()
{
await Task.Yield();
}

public async Task TaskMethodWithoutAwait()
{
Console.WriteLine("No Await");
Expand Down Expand Up @@ -115,6 +121,24 @@ public async void AwaitInLoopCondition()
}
}

public async Task AwaitConfigureAwaitFalse(Task<int> task)
{
#if ROSLYN2
Console.WriteLine(await task.ConfigureAwait(continueOnCapturedContext: false));
#else
Console.WriteLine(await task.ConfigureAwait(false));
#endif
}

public async Task<int> AwaitConfigureAwaitMixed(Task<int> task1, Task<int> task2)
{
#if ROSLYN2
return await task1.ConfigureAwait(continueOnCapturedContext: false) + await task2.ConfigureAwait(continueOnCapturedContext: true);
#else
return await task1.ConfigureAwait(false) + await task2.ConfigureAwait(true);
#endif
}

#if CS60
public async Task AwaitInCatch(bool b, Task<int> task1, Task<int> task2)
{
Expand Down
3 changes: 2 additions & 1 deletion ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public enum LanguageVersion
CSharp12_0 = 1200,
CSharp13_0 = 1300,
CSharp14_0 = 1400,
Preview = 1400,
CSharp15_0 = 1500,
Preview = 1500,
Latest = 0x7FFFFFFF
}
}
24 changes: 24 additions & 0 deletions ICSharpCode.Decompiler/DecompilerSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,16 @@ public void SetLanguageVersion(CSharp.LanguageVersion languageVersion)
extensionMembers = false;
firstClassSpanTypes = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp15_0)
{
runtimeAsync = false;
}
}

public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (runtimeAsync)
return CSharp.LanguageVersion.CSharp15_0;
if (extensionMembers || firstClassSpanTypes)
return CSharp.LanguageVersion.CSharp14_0;
if (paramsCollections)
Expand Down Expand Up @@ -2198,6 +2204,24 @@ public bool FirstClassSpanTypes {
}
}

bool runtimeAsync = true;

/// <summary>
/// Gets/Sets whether runtime async should be used.
/// </summary>
[Category("C# 15.0 / VS 202x.yy")]
[Description("DecompilerSettings.RuntimeAsync")]
public bool RuntimeAsync {
get { return runtimeAsync; }
set {
if (runtimeAsync != value)
{
runtimeAsync = value;
OnPropertyChanged();
}
}
}

bool separateLocalVariableDeclarations = false;

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@
<Compile Include="Disassembler\DisassemblerSignatureTypeProvider.cs" />
<Compile Include="Documentation\XmlDocumentationElement.cs" />
<Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" />
<Compile Include="IL\ControlFlow\RuntimeAsyncExceptionRewriteTransform.cs" />
<Compile Include="IL\ControlFlow\RuntimeAsyncManualAwaitTransform.cs" />
<Compile Include="IL\Transforms\InterpolatedStringTransform.cs" />
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />
Expand Down
54 changes: 54 additions & 0 deletions ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,15 @@ public void Run(ILFunction function, ILTransformContext context)
awaitDebugInfos.Clear();
moveNextLeaves.Clear();
if (!MatchTaskCreationPattern(function) && !MatchAsyncEnumeratorCreationPattern(function))
{
if (function.IsAsync && context.Settings.RuntimeAsync)
{
TranslateThisCopyAccess(function);
RuntimeAsyncManualAwaitTransform.Run(function, context);
RuntimeAsyncExceptionRewriteTransform.Run(function, context);
}
return;
}
try
{
AnalyzeMoveNext();
Expand Down Expand Up @@ -174,6 +182,52 @@ public void Run(ILFunction function, ILTransformContext context)
function.AsyncDebugInfo = new AsyncDebugInfo(catchHandlerOffset, awaitDebugInfos.ToImmutableArray());
}

// Runtime-async analog of fieldToParameterMap's `<>4__this` capture: in a struct method,
// Roslyn lowers `this` references to a single ldobj at function entry stored into a local.
// Match the entry instruction `stloc V_X(ldobj T(ldloc this))` and rewrite every later
// `ldloc V_X` / `ldloca V_X` to go through the `this` parameter again, so AST emission
// renders accesses as plain `this.field` / `field` rather than `<copy>.field`.
static bool TranslateThisCopyAccess(ILFunction function)
{
var thisParam = function.Variables.FirstOrDefault(v => v.Kind == VariableKind.Parameter && v.Index == -1);
if (thisParam == null)
return false;
if (function.Body is not BlockContainer body || body.Blocks.Count == 0)
return false;
var entry = body.EntryPoint;
if (entry.Instructions.Count == 0)
return false;
if (entry.Instructions[0] is not StLoc thisCopyStore)
return false;
var copyVar = thisCopyStore.Variable;
if (copyVar.Kind != VariableKind.Local)
return false;
if (copyVar.StoreInstructions.Count != 1)
return false;
if (thisCopyStore.Value is not LdObj ldobj)
return false;
if (!ldobj.Target.MatchLdLoc(thisParam))
return false;
if (!copyVar.Type.Equals(ldobj.Type))
return false;

foreach (var ldloc in function.Descendants.OfType<LdLoc>().ToArray())
{
if (ldloc.Variable != copyVar)
continue;
ldloc.ReplaceWith(new LdObj(new LdLoc(thisParam), ldobj.Type).WithILRange(ldloc));
}
foreach (var ldloca in function.Descendants.OfType<LdLoca>().ToArray())
{
if (ldloca.Variable != copyVar)
continue;
ldloca.ReplaceWith(new LdLoc(thisParam).WithILRange(ldloca));
}

entry.Instructions.RemoveAt(0);
return true;
}

private void CleanUpBodyOfMoveNext(ILFunction function)
{
context.StepStartGroup("CleanUpBodyOfMoveNext", function);
Expand Down
Loading
Loading