|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ILCompiler;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
using Internal.IL.Stubs;
using System.Buffers.Binary;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ILCompiler.ReadyToRun.TypeSystem;
using ILCompiler.DependencyAnalysis.ReadyToRun;
namespace Internal.IL
{
/// <summary>
/// Marker interface that promises that all tokens from this MethodIL are useable in the current compilation
/// </summary>
public interface IMethodTokensAreUseableInCompilation { }
public sealed class ReadyToRunILProvider : ILProvider
{
private CompilationModuleGroup _compilationModuleGroup;
private MutableModule _manifestMutableModule;
private int _version = 0;
public ReadyToRunILProvider(CompilationModuleGroup compilationModuleGroup)
{
_compilationModuleGroup = compilationModuleGroup;
}
public void InitManifestMutableModule(MutableModule module)
{
_manifestMutableModule = module;
}
void IncrementVersion()
{
_version++;
}
public int Version => _version;
private MethodIL TryGetIntrinsicMethodILForActivator(MethodDesc method)
{
if (method.Instantiation.Length == 1
&& method.Signature.Length == 0
&& method.Name.SequenceEqual("CreateInstance"u8))
{
TypeDesc type = method.Instantiation[0];
if (type.IsValueType && type.GetParameterlessConstructor() == null)
{
// Replace the body with implementation that just returns "default"
MethodDesc createDefaultInstance = method.OwningType.GetKnownMethod("CreateDefaultInstance"u8, method.GetTypicalMethodDefinition().Signature);
return GetMethodIL(createDefaultInstance.MakeInstantiatedMethod(type));
}
}
return null;
}
/// <summary>
/// Provides method bodies for intrinsics recognized by the compiler.
/// It can return null if it's not an intrinsic recognized by the compiler,
/// but an intrinsic e.g. recognized by codegen.
/// </summary>
private MethodIL TryGetIntrinsicMethodIL(MethodDesc method)
{
var mdType = method.OwningType as MetadataType;
if (mdType == null)
return null;
if (mdType.Name.SequenceEqual("RuntimeHelpers"u8) && mdType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8))
{
return RuntimeHelpersIntrinsics.EmitIL(method);
}
if (mdType.Name.SequenceEqual("Unsafe"u8) && mdType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8))
{
return UnsafeIntrinsics.EmitIL(method);
}
if (mdType.Name.SequenceEqual("InstanceCalliHelper"u8) && mdType.Namespace.SequenceEqual("System.Reflection"u8))
{
return InstanceCalliHelperIntrinsics.EmitIL(method);
}
return null;
}
/// <summary>
/// Provides method bodies for intrinsics recognized by the compiler that
/// are specialized per instantiation. It can return null if the intrinsic
/// is not recognized.
/// </summary>
private MethodIL TryGetPerInstantiationIntrinsicMethodIL(MethodDesc method)
{
var mdType = method.OwningType as MetadataType;
if (mdType == null)
return null;
if (mdType.Name.SequenceEqual("RuntimeHelpers"u8) && mdType.Namespace.SequenceEqual("System.Runtime.CompilerServices"u8))
{
return RuntimeHelpersIntrinsics.EmitIL(method);
}
if (mdType.Name.SequenceEqual("Activator"u8) && mdType.Namespace.SequenceEqual("System"u8))
{
return TryGetIntrinsicMethodILForActivator(method);
}
if (mdType.Name.SequenceEqual("Interlocked"u8) && mdType.Namespace.SequenceEqual("System.Threading"u8))
{
return InterlockedIntrinsics.EmitIL(_compilationModuleGroup, method);
}
if (mdType.Namespace.SequenceEqual("System.Collections.Generic"u8))
{
if (mdType.Name.SequenceEqual("Comparer`1"u8))
{
if (method.Name.SequenceEqual("Create"u8))
return ComparerIntrinsics.EmitComparerCreate(method);
}
else if (mdType.Name.SequenceEqual("EqualityComparer`1"u8))
{
if (method.Name.SequenceEqual("Create"u8))
return ComparerIntrinsics.EmitEqualityComparerCreate(method);
}
}
return null;
}
private Dictionary<MethodDesc, MethodIL> _manifestModuleWrappedMethods = new Dictionary<MethodDesc, MethodIL>();
// Create the cross module inlineable tokens for a method
// This method is order dependent, and must be called during the single threaded portion of compilation
public void CreateCrossModuleInlineableTokensForILBody(MethodDesc method)
{
// This method accepts only method definitions, but accepts non-primary method definitions.
// That is, it must not be generic, but it may represent an AsyncVariant
Debug.Assert(method.IsTypicalMethodDefinition);
Debug.Assert(_manifestMutableModule != null);
var wrappedMethodIL = new ManifestModuleWrappedMethodIL();
Debug.Assert(!_compilationModuleGroup.VersionsWithMethodBody(method) &&
_compilationModuleGroup.CrossModuleInlineable(method));
if (!wrappedMethodIL.Initialize(_manifestMutableModule, EcmaMethodIL.Create((EcmaMethod)method.GetPrimaryMethodDesc().GetTypicalMethodDefinition())))
{
// If we could not initialize the wrapped method IL, we should store a null.
// That will result in the IL code for the method being unavailable for use in
// the compilation, which is version safe.
wrappedMethodIL = null;
}
_manifestModuleWrappedMethods.Add(method, wrappedMethodIL);
IncrementVersion();
}
public bool NeedsCrossModuleInlineableTokens(MethodDesc method)
{
bool regularCrossModuleInlineable = (!_compilationModuleGroup.VersionsWithMethodBody(method)
&& _compilationModuleGroup.CrossModuleInlineable(method));
if ((regularCrossModuleInlineable)
&& !_manifestModuleWrappedMethods.ContainsKey(method))
{
return true;
}
return false;
}
private static bool NeedsTaskReturningThunk(MethodDesc method)
{
Debug.Assert(method.IsTypicalMethodDefinition);
if (method is not EcmaMethod ecmaMethod)
return false;
if (!method.IsAsync)
return false;
if (method.Signature.ReturnsTaskOrValueTask())
return true;
if (ecmaMethod.OwningType.Module != ecmaMethod.Context.SystemModule)
return true;
return false;
}
private static bool NeedsAsyncThunk(MethodDesc method)
{
Debug.Assert(method.IsTypicalMethodDefinition);
if (method is not AsyncMethodVariant)
return false;
return !method.IsAsync;
}
private MethodIL GetMethodILForAsyncMethod(MethodDesc method)
{
Debug.Assert(method.IsAsync && method is EcmaMethod);
if (method.Signature.ReturnsTaskOrValueTask())
{
return AsyncThunkILEmitter.EmitTaskReturningThunk(method, method.GetAsyncVariant());
}
// We only allow non-Task returning runtime async methods in CoreLib
// Skip this method
return null;
}
public override MethodIL GetMethodIL(MethodDesc method)
{
if (method is EcmaMethod ecmaMethod)
{
if (method.IsIntrinsic)
{
MethodIL result = TryGetIntrinsicMethodIL(method);
if (result != null)
return result;
}
// Check to see if there is an override for the EcmaMethodIL. If there is not
// then simply return the EcmaMethodIL. In theory this could call
// CreateCrossModuleInlineableTokensForILBody, but we explicitly do not want
// to do that. The reason is that this method is called during the multithreaded
// portion of compilation, and CreateCrossModuleInlineableTokensForILBody
// will produce tokens which are order dependent thus violating the determinism
// principles of the compiler.
if (_manifestModuleWrappedMethods.TryGetValue(ecmaMethod, out var methodIL))
return methodIL;
return NeedsTaskReturningThunk(ecmaMethod) ?
GetMethodILForAsyncMethod(ecmaMethod)
: EcmaMethodIL.Create(ecmaMethod);
}
else if (method is AsyncMethodVariant amv)
{
if (_manifestModuleWrappedMethods.TryGetValue(amv, out var methodIL))
return methodIL;
return NeedsAsyncThunk(amv) ?
AsyncThunkILEmitter.EmitAsyncMethodThunk(amv, amv.Target)
: new AsyncEcmaMethodIL(amv, EcmaMethodIL.Create(amv.Target));
}
else if (method is MethodForInstantiatedType || method is InstantiatedMethod)
{
// Intrinsics specialized per instantiation
if (method.IsIntrinsic)
{
MethodIL methodIL = TryGetPerInstantiationIntrinsicMethodIL(method);
if (methodIL != null)
return methodIL;
}
var methodDefinitionIL = GetMethodIL(method.GetTypicalMethodDefinition());
if (methodDefinitionIL == null)
return null;
return new InstantiatedMethodIL(method, methodDefinitionIL);
}
else if (method is AsyncResumptionStub ars)
{
return ars.EmitIL();
}
else
{
return null;
}
}
/// <summary>
/// A MethodIL Provider which provides tokens relative to a MutableModule. Used to implement cross
/// module inlining of code in ReadyToRun files.
/// </summary>
class ManifestModuleWrappedMethodIL : MethodIL, IEcmaMethodIL, IMethodTokensAreUseableInCompilation
{
int _maxStack;
bool _isInitLocals;
MethodDesc _owningMethod;
ILExceptionRegion[] _exceptionRegions;
byte[] _ilBytes;
LocalVariableDefinition[] _locals;
MutableModule _mutableModule;
public ManifestModuleWrappedMethodIL() {}
public bool Initialize(MutableModule mutableModule, EcmaMethodIL wrappedMethod)
{
bool failedToReplaceToken = false;
try
{
var owningMethod = wrappedMethod.OwningMethod;
Debug.Assert(mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences == null);
mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = ((EcmaMethod)owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()).Module;
var owningMethodHandle = mutableModule.TryGetEntityHandle(owningMethod);
if (!owningMethodHandle.HasValue)
return false;
_mutableModule = mutableModule;
_maxStack = wrappedMethod.MaxStack;
_isInitLocals = wrappedMethod.IsInitLocals;
_owningMethod = owningMethod;
_exceptionRegions = (ILExceptionRegion[])wrappedMethod.GetExceptionRegions().Clone();
_ilBytes = (byte[])wrappedMethod.GetILBytes().Clone();
_locals = (LocalVariableDefinition[])wrappedMethod.GetLocals();
for (int i = 0; i < _exceptionRegions.Length; i++)
{
var region = _exceptionRegions[i];
if (region.Kind == ILExceptionRegionKind.Catch)
{
var newHandle = _mutableModule.TryGetHandle((TypeSystemEntity)wrappedMethod.GetObject(region.ClassToken));
if (!newHandle.HasValue)
{
return false;
}
_exceptionRegions[i] = new ILExceptionRegion(region.Kind, region.TryOffset, region.TryLength, region.HandlerOffset, region.HandlerLength, newHandle.Value, newHandle.Value);
}
}
ILTokenReplacer.Replace(_ilBytes, GetMutableModuleToken);
#if DEBUG
Debug.Assert(ReadyToRunStandaloneMethodMetadata.Compute((EcmaMethod)_owningMethod.GetPrimaryMethodDesc().GetTypicalMethodDefinition()) != null);
#endif // DEBUG
}
finally
{
mutableModule.ModuleThatIsCurrentlyTheSourceOfNewReferences = null;
}
return !failedToReplaceToken;
int GetMutableModuleToken(int token)
{
object result = wrappedMethod.GetObject(token);
int? newToken;
if (result is string str)
{
newToken = mutableModule.TryGetStringHandle(str);
}
else
{
newToken = mutableModule.TryGetHandle((TypeSystemEntity)result);
}
if (!newToken.HasValue)
{
// Token replacement has failed. Do not attempt to use this IL.
failedToReplaceToken = true;
return 1;
}
return newToken.Value;
}
}
public override int MaxStack => _maxStack;
public override bool IsInitLocals => _isInitLocals;
public override MethodDesc OwningMethod => _owningMethod;
public IEcmaModule Module => _mutableModule;
public override ILExceptionRegion[] GetExceptionRegions() => _exceptionRegions;
public override byte[] GetILBytes() => _ilBytes;
public override LocalVariableDefinition[] GetLocals() => _locals;
public override object GetObject(int token, NotFoundBehavior notFoundBehavior = NotFoundBehavior.Throw)
{
// UserStrings cannot be wrapped in EntityHandle
if ((token & 0xFF000000) == 0x70000000)
return _mutableModule.GetUserString(System.Reflection.Metadata.Ecma335.MetadataTokens.UserStringHandle(token));
return _mutableModule.GetObject(System.Reflection.Metadata.Ecma335.MetadataTokens.EntityHandle(token), notFoundBehavior);
}
}
public sealed class AsyncEcmaMethodIL : MethodIL, IEcmaMethodIL
{
private readonly AsyncMethodVariant _variant;
private readonly EcmaMethodIL _ecmaIL;
public AsyncEcmaMethodIL(AsyncMethodVariant variant, EcmaMethodIL ecmaIL)
=> (_variant, _ecmaIL) = (variant, ecmaIL);
// This is the reason we need this class - the method that owns the IL is the variant.
public override MethodDesc OwningMethod => _variant;
// Everything else dispatches to EcmaMethodIL
public override MethodDebugInformation GetDebugInfo() => _ecmaIL.GetDebugInfo();
public override ILExceptionRegion[] GetExceptionRegions() => _ecmaIL.GetExceptionRegions();
public override byte[] GetILBytes() => _ecmaIL.GetILBytes();
public override LocalVariableDefinition[] GetLocals() => _ecmaIL.GetLocals();
public override object GetObject(int token, NotFoundBehavior notFoundBehavior = NotFoundBehavior.Throw) => _ecmaIL.GetObject(token, notFoundBehavior);
public override bool IsInitLocals => _ecmaIL.IsInitLocals;
public override int MaxStack => _ecmaIL.MaxStack;
public IEcmaModule Module => _ecmaIL.Module;
}
}
}
|