Browse Source

Add scaffolding for the C# 15 runtime-async lowering.

Detect MethodImplOptions.Async (0x2000) in ILReader and unpack Task/Task<T>
return types so the IL Leave value and function.AsyncReturnType match the
source signature. Add CSharp15_0 (Preview also bumped to 1500) and a
RuntimeAsync setting (default on, gated to >=CSharp15_0), expose it in the
Languages dropdown, mask the synthetic MethodImplAsync bit out of the
decompiled [MethodImpl], and add a .runtimeasync test suffix.
pull/3731/head
Siegfried Pammer 1 month ago
parent
commit
82edef3bc6
  1. 2
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  2. 3
      ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs
  3. 28
      ICSharpCode.Decompiler/DecompilerSettings.cs
  4. 19
      ICSharpCode.Decompiler/IL/ILReader.cs
  5. 1
      ICSharpCode.Decompiler/SRMHacks.cs
  6. 7
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  7. 4
      ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs
  8. 3
      ILSpy/Languages/CSharpLanguage.cs
  9. 11
      ILSpy/Properties/Resources.Designer.cs
  10. 3
      ILSpy/Properties/Resources.resx

2
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -848,6 +848,8 @@ namespace System.Runtime.CompilerServices @@ -848,6 +848,8 @@ namespace System.Runtime.CompilerServices
suffix += ".mcs2";
if ((cscOptions & CompilerOptions.UseMcs5_23) != 0)
suffix += ".mcs5";
if ((cscOptions & CompilerOptions.EnableRuntimeAsync) != 0)
suffix += ".runtimeasync";
return suffix;
}

3
ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs

@ -37,7 +37,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -37,7 +37,8 @@ namespace ICSharpCode.Decompiler.CSharp
CSharp12_0 = 1200,
CSharp13_0 = 1300,
CSharp14_0 = 1400,
Preview = 1400,
CSharp15_0 = 1500,
Preview = 1500,
Latest = 0x7FFFFFFF
}
}

28
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -177,10 +177,16 @@ namespace ICSharpCode.Decompiler @@ -177,10 +177,16 @@ namespace ICSharpCode.Decompiler
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)
@ -2167,7 +2173,7 @@ namespace ICSharpCode.Decompiler @@ -2167,7 +2173,7 @@ namespace ICSharpCode.Decompiler
/// <summary>
/// Gets/Sets whether C# 14.0 extension members should be transformed.
/// </summary>
[Category("C# 14.0 / VS 202x.yy")]
[Category("C# 14.0 / VS 2026")]
[Description("DecompilerSettings.ExtensionMembers")]
public bool ExtensionMembers {
get { return extensionMembers; }
@ -2185,7 +2191,7 @@ namespace ICSharpCode.Decompiler @@ -2185,7 +2191,7 @@ namespace ICSharpCode.Decompiler
/// <summary>
/// Gets/Sets whether (ReadOnly)Span&lt;T&gt; should be treated like built-in types.
/// </summary>
[Category("C# 14.0 / VS 202x.yy")]
[Category("C# 14.0 / VS 2026")]
[Description("DecompilerSettings.FirstClassSpanTypes")]
public bool FirstClassSpanTypes {
get { return firstClassSpanTypes; }
@ -2198,6 +2204,24 @@ namespace ICSharpCode.Decompiler @@ -2198,6 +2204,24 @@ namespace ICSharpCode.Decompiler
}
}
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>

19
ICSharpCode.Decompiler/IL/ILReader.cs

@ -159,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL @@ -159,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL
BitSet isBranchTarget = null!;
BlockContainer mainContainer = null!;
int currentInstructionStart;
bool isRuntimeAsync;
Dictionary<int, ImportedBlock> blocksByOffset = new Dictionary<int, ImportedBlock>();
Queue<ImportedBlock> importQueue = new Queue<ImportedBlock>();
@ -172,6 +173,8 @@ namespace ICSharpCode.Decompiler.IL @@ -172,6 +173,8 @@ namespace ICSharpCode.Decompiler.IL
if (methodDefinitionHandle.IsNil)
throw new ArgumentException("methodDefinitionHandle.IsNil");
this.method = module.GetDefinition(methodDefinitionHandle);
this.isRuntimeAsync = (module.TypeSystemOptions & TypeSystemOptions.RuntimeAsync) != 0
&& (module.metadata.GetMethodDefinition(methodDefinitionHandle).ImplAttributes & SRMExtensions.MethodImplAsync) != 0;
if (genericContext.ClassTypeParameters == null && genericContext.MethodTypeParameters == null)
{
// no generic context specified: use the method's own type parameters
@ -187,7 +190,14 @@ namespace ICSharpCode.Decompiler.IL @@ -187,7 +190,14 @@ namespace ICSharpCode.Decompiler.IL
this.reader = body.GetILReader();
this.currentStack = ImmutableStack<ILVariable>.Empty;
this.expressionStack.Clear();
this.methodReturnStackType = method.ReturnType.GetStackType();
if (isRuntimeAsync)
{
this.methodReturnStackType = TaskType.UnpackTask(compilation, method.ReturnType).GetStackType();
}
else
{
this.methodReturnStackType = method.ReturnType.GetStackType();
}
InitParameterVariables();
localVariables = InitLocalVariables();
foreach (var v in localVariables)
@ -715,6 +725,13 @@ namespace ICSharpCode.Decompiler.IL @@ -715,6 +725,13 @@ namespace ICSharpCode.Decompiler.IL
var blockBuilder = new BlockBuilder(body, variableByExceptionHandler, compilation);
blockBuilder.CreateBlocks(mainContainer, blocksByOffset.Values.Select(ib => ib.Block), cancellationToken);
var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind);
if (isRuntimeAsync)
{
if (!this.method.ReturnType.IsKnownType(KnownTypeCode.TaskOfT))
function.AsyncReturnType = compilation.FindType(KnownTypeCode.Void);
else
function.AsyncReturnType = TaskType.UnpackTask(compilation, this.method.ReturnType);
}
function.Variables.AddRange(parameterVariables);
function.Variables.AddRange(localVariables);
function.LocalVariableSignatureLength = localVariables.Length;

1
ICSharpCode.Decompiler/SRMHacks.cs

@ -15,6 +15,7 @@ namespace ICSharpCode.Decompiler @@ -15,6 +15,7 @@ namespace ICSharpCode.Decompiler
public static partial class SRMExtensions
{
internal const GenericParameterAttributes AllowByRefLike = (GenericParameterAttributes)0x0020;
internal const MethodImplAttributes MethodImplAsync = (MethodImplAttributes)0x2000;
public static ImmutableArray<MethodImplementationHandle> GetMethodImplementations(
this MethodDefinitionHandle handle, MetadataReader reader)

7
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -154,12 +154,17 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -154,12 +154,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
ExtensionMembers = 0x80000,
/// <summary>
/// If this option is active, methods with the MethodImplAttribute(MethodImplOptions.Async) are treated as async methods.
/// </summary>
RuntimeAsync = 0x100000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# language features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute
| RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes | ExtensionMembers
| RuntimeAsync
}
/// <summary>
@ -207,6 +212,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -207,6 +212,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
typeSystemOptions |= TypeSystemOptions.FirstClassSpanTypes;
if (settings.ExtensionMembers)
typeSystemOptions |= TypeSystemOptions.ExtensionMembers;
if (settings.RuntimeAsync)
typeSystemOptions |= TypeSystemOptions.RuntimeAsync;
return typeSystemOptions;
}

4
ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataMethod.cs

@ -350,6 +350,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -350,6 +350,10 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
var metadata = module.metadata;
var def = metadata.GetMethodDefinition(handle);
MethodImplAttributes implAttributes = def.ImplAttributes & ~MethodImplAttributes.CodeTypeMask;
if ((module.TypeSystemOptions & TypeSystemOptions.RuntimeAsync) != 0)
{
implAttributes &= ~SRMExtensions.MethodImplAsync;
}
int methodCodeType = (int)(def.ImplAttributes & MethodImplAttributes.CodeTypeMask);
#region DllImportAttribute

3
ILSpy/Languages/CSharpLanguage.cs

@ -24,8 +24,6 @@ using System.Linq; @@ -24,8 +24,6 @@ using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Windows;
using System.Windows.Controls;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler;
@ -119,6 +117,7 @@ namespace ICSharpCode.ILSpy @@ -119,6 +117,7 @@ namespace ICSharpCode.ILSpy
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp12_0.ToString(), "C# 12.0 / VS 2022.8"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp13_0.ToString(), "C# 13.0 / VS 2022.12"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp14_0.ToString(), "C# 14.0 / VS 2026"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp15_0.ToString(), "C# 15.0 / VS 202x.yy"),
};
}
return versions;

11
ILSpy/Properties/Resources.Designer.cs generated

@ -1360,7 +1360,16 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1360,7 +1360,16 @@ namespace ICSharpCode.ILSpy.Properties {
return ResourceManager.GetString("DecompilerSettings.RequiredMembers", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Decompile runtime-async (System.Runtime.CompilerServices.AsyncHelpers) methods.
/// </summary>
public static string DecompilerSettings_RuntimeAsync {
get {
return ResourceManager.GetString("DecompilerSettings.RuntimeAsync", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;scoped&apos; lifetime annotation.
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -474,6 +474,9 @@ Are you sure you want to continue?</value> @@ -474,6 +474,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.RequiredMembers" xml:space="preserve">
<value>Required members</value>
</data>
<data name="DecompilerSettings.RuntimeAsync" xml:space="preserve">
<value>Decompile runtime-async (System.Runtime.CompilerServices.AsyncHelpers) methods</value>
</data>
<data name="DecompilerSettings.ScopedRef" xml:space="preserve">
<value>'scoped' lifetime annotation</value>
</data>

Loading…
Cancel
Save