From 6c9383841efb12963b898254f14b707ac730dcb7 Mon Sep 17 00:00:00 2001 From: Fadi Hanna Date: Wed, 20 Nov 2019 19:09:43 -0800 Subject: [PATCH] Use precompiled PInvoke stubs from R2R image without the shared IL stub (#101) * Use precompiled PInvoke stubs from R2R image without a shared IL stub This is only for images compiled with crossgen2. Changes include some refactoring work around the signature parsing to compute marshalling requirements. We had two separate implementations after pulling the PInvoke work from the single-exe branch. Consolidated into one implementation. --- src/coreclr/src/inc/readytorun.h | 9 +- .../Common/Internal/Runtime/ModuleHeaders.cs | 8 ++ .../crossgen2/Common/JitInterface/CorInfoImpl.cs | 17 +-- .../Stubs/PInvokeTargetNativeMethod.Diagnostic.cs | 19 ++++ .../IL/Stubs/PInvokeTargetNativeMethod.Mangling.cs | 27 +++++ .../IL/Stubs/PInvokeTargetNativeMethod.Sorting.cs | 20 ++++ .../IL/Stubs/PInvokeTargetNativeMethod.cs | 92 +++++++++++++++++ .../Common/TypeSystem/Interop/IL/Marshaller.cs | 115 ++++++++++++++++++--- .../DependencyAnalysis/ReadyToRun/HeaderNode.cs | 2 +- .../Compiler/MethodExtensions.cs | 22 ++++ .../Compiler/ReadyToRunCodegenCompilation.cs | 3 +- ...adyToRunSingleAssemblyCompilationModuleGroup.cs | 18 ++-- .../IL/Stubs/PInvokeILEmitter.cs | 86 +++------------ .../ILCompiler.ReadyToRun.csproj | 1 + .../JitInterface/CorInfoImpl.ReadyToRun.cs | 112 +++++--------------- .../ILCompiler.TypeSystem.ReadyToRun.csproj | 12 +++ src/coreclr/src/tools/r2rdump/R2RHeader.cs | 6 +- src/coreclr/src/vm/prestub.cpp | 67 +++++++----- src/coreclr/src/vm/readytoruninfo.h | 6 ++ 19 files changed, 417 insertions(+), 225 deletions(-) create mode 100644 src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Diagnostic.cs create mode 100644 src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Mangling.cs create mode 100644 src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Sorting.cs create mode 100644 src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.cs create mode 100644 src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs diff --git a/src/coreclr/src/inc/readytorun.h b/src/coreclr/src/inc/readytorun.h index 514bb0c..c78ec74 100644 --- a/src/coreclr/src/inc/readytorun.h +++ b/src/coreclr/src/inc/readytorun.h @@ -45,11 +45,10 @@ struct READYTORUN_SECTION enum ReadyToRunFlag { - // Set if the original IL assembly was platform-neutral - READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE = 0x00000001, - READYTORUN_FLAG_SKIP_TYPE_VALIDATION = 0x00000002, - // Set of methods with native code was determined using profile data - READYTORUN_FLAG_PARTIAL = 0x00000004, + READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE = 0x00000001, // Set if the original IL assembly was platform-neutral + READYTORUN_FLAG_SKIP_TYPE_VALIDATION = 0x00000002, // Set of methods with native code was determined using profile data + READYTORUN_FLAG_PARTIAL = 0x00000004, + READYTORUN_FLAG_NONSHARED_PINVOKE_STUBS = 0x00000008 // PInvoke stubs compiled into image are non-shareable (no secret parameter) }; enum ReadyToRunSectionType diff --git a/src/coreclr/src/tools/crossgen2/Common/Internal/Runtime/ModuleHeaders.cs b/src/coreclr/src/tools/crossgen2/Common/Internal/Runtime/ModuleHeaders.cs index c18b0d0..5c37431 100644 --- a/src/coreclr/src/tools/crossgen2/Common/Internal/Runtime/ModuleHeaders.cs +++ b/src/coreclr/src/tools/crossgen2/Common/Internal/Runtime/ModuleHeaders.cs @@ -36,6 +36,14 @@ namespace Internal.Runtime }; #pragma warning restore 0169 + enum ReadyToRunFlag + { + READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE = 0x00000001, // Set if the original IL assembly was platform-neutral + READYTORUN_FLAG_SKIP_TYPE_VALIDATION = 0x00000002, // Set of methods with native code was determined using profile data + READYTORUN_FLAG_PARTIAL = 0x00000004, + READYTORUN_FLAG_NONSHARED_PINVOKE_STUBS = 0x00000008 // PInvoke stubs compiled into image are non-shareable (no secret parameter) + }; + // // ReadyToRunSectionType IDs are used by the runtime to look up specific global data sections // from each module linked into the final binary. New sections should be added at the bottom diff --git a/src/coreclr/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs index c8aa989..86fdeea 100644 --- a/src/coreclr/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs @@ -659,6 +659,11 @@ namespace Internal.JitInterface if (method.IsPInvoke) { result |= CorInfoFlag.CORINFO_FLG_PINVOKE; + + if (method.IsRawPInvoke()) + { + result |= CorInfoFlag.CORINFO_FLG_FORCEINLINE; + } } #if READYTORUN @@ -774,14 +779,6 @@ namespace Internal.JitInterface MethodDesc callerMethod = HandleToObject(callerHnd); MethodDesc calleeMethod = HandleToObject(calleeHnd); -#if READYTORUN - // IL stubs don't inline well - if (calleeMethod.IsPInvoke) - { - return CorInfoInline.INLINE_NEVER; - } -#endif - if (_compilation.CanInline(callerMethod, calleeMethod)) { // No restrictions on inlining @@ -3066,10 +3063,6 @@ namespace Internal.JitInterface if (this.MethodBeingCompiled.IsPInvoke) { flags.Set(CorJitFlag.CORJIT_FLAG_IL_STUB); - -#if READYTORUN - flags.Set(CorJitFlag.CORJIT_FLAG_PUBLISH_SECRET_PARAM); -#endif } if (this.MethodBeingCompiled.IsNoOptimization) diff --git a/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Diagnostic.cs b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Diagnostic.cs new file mode 100644 index 0000000..2cb4d1e --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Diagnostic.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace Internal.IL.Stubs +{ + partial class PInvokeTargetNativeMethod + { + public override string DiagnosticName + { + get + { + return _declMethod.DiagnosticName; + } + } + } +} diff --git a/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Mangling.cs b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Mangling.cs new file mode 100644 index 0000000..79ea39a --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Mangling.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace Internal.IL.Stubs +{ + partial class PInvokeTargetNativeMethod : IPrefixMangledMethod + { + MethodDesc IPrefixMangledMethod.BaseMethod + { + get + { + return _declMethod; + } + } + + string IPrefixMangledMethod.Prefix + { + get + { + return "rawpinvoke"; + } + } + } +} diff --git a/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Sorting.cs b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Sorting.cs new file mode 100644 index 0000000..ea75434 --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.Sorting.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace Internal.IL.Stubs +{ + // Functionality related to deterministic ordering of types + partial class PInvokeTargetNativeMethod + { + protected internal override int ClassCode => -1626939381; + + protected internal override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + var otherMethod = (PInvokeTargetNativeMethod)other; + return comparer.Compare(_declMethod, otherMethod._declMethod); + } + } +} diff --git a/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.cs b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.cs new file mode 100644 index 0000000..519dee4 --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/IL/Stubs/PInvokeTargetNativeMethod.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace Internal.IL.Stubs +{ + /// + /// Synthetic method that represents the actual PInvoke target method. + /// All parameters are simple types. There will be no code + /// generated for this method. Instead, a static reference to a symbol will be emitted. + /// + public sealed partial class PInvokeTargetNativeMethod : MethodDesc + { + private readonly MethodDesc _declMethod; + private readonly MethodSignature _signature; + + public MethodDesc Target + { + get + { + return _declMethod; + } + } + + public PInvokeTargetNativeMethod(MethodDesc declMethod, MethodSignature signature) + { + _declMethod = declMethod; + _signature = signature; + } + + public override TypeSystemContext Context + { + get + { + return _declMethod.Context; + } + } + + public override TypeDesc OwningType + { + get + { + return _declMethod.OwningType; + } + } + + public override MethodSignature Signature + { + get + { + return _signature; + } + } + + public override string Name + { + get + { + return _declMethod.Name; + } + } + + public override bool HasCustomAttribute(string attributeNamespace, string attributeName) + { + return false; + } + + public override bool IsPInvoke + { + get + { + return true; + } + } + + public override bool IsNoInlining + { + get + { + // This method does not have real IL body. NoInlining stops the JIT asking for it. + return true; + } + } + + public override PInvokeMetadata GetPInvokeMethodMetadata() + { + return _declMethod.GetPInvokeMethodMetadata(); + } + } +} diff --git a/src/coreclr/src/tools/crossgen2/Common/TypeSystem/Interop/IL/Marshaller.cs b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/Interop/IL/Marshaller.cs index 624ff45..2c27239 100644 --- a/src/coreclr/src/tools/crossgen2/Common/TypeSystem/Interop/IL/Marshaller.cs +++ b/src/coreclr/src/tools/crossgen2/Common/TypeSystem/Interop/IL/Marshaller.cs @@ -155,7 +155,7 @@ namespace Internal.TypeSystem.Interop protected PInvokeILCodeStreams _ilCodeStreams; protected Home _managedHome; protected Home _nativeHome; -#endregion + #endregion enum HomeType { @@ -255,7 +255,7 @@ namespace Internal.TypeSystem.Interop int _argIndex; } -#region Creation of marshallers + #region Creation of marshallers /// /// Protected ctor @@ -365,12 +365,54 @@ namespace Internal.TypeSystem.Interop return marshaller; } - public bool IsMarshallingRequired() + public static Marshaller[] GetMarshallersForMethod(MethodDesc targetMethod) { - if (Out) - return true; + Debug.Assert(targetMethod.IsPInvoke); + + MarshalDirection direction = MarshalDirection.Forward; + MethodSignature methodSig = targetMethod.Signature; + PInvokeFlags flags = targetMethod.GetPInvokeMethodMetadata().Flags; + + ParameterMetadata[] parameterMetadataArray = targetMethod.GetParameterMetadata(); + Marshaller[] marshallers = new Marshaller[methodSig.Length + 1]; + ParameterMetadata parameterMetadata; + + for (int i = 0, parameterIndex = 0; i < marshallers.Length; i++) + { + Debug.Assert(parameterIndex == parameterMetadataArray.Length || i <= parameterMetadataArray[parameterIndex].Index); + if (parameterIndex == parameterMetadataArray.Length || i < parameterMetadataArray[parameterIndex].Index) + { + // if we don't have metadata for the parameter, create a dummy one + parameterMetadata = new ParameterMetadata(i, ParameterMetadataAttributes.None, null); + } + else + { + Debug.Assert(i == parameterMetadataArray[parameterIndex].Index); + parameterMetadata = parameterMetadataArray[parameterIndex++]; + } - switch (MarshallerKind) + TypeDesc parameterType = (i == 0) ? methodSig.ReturnType : methodSig[i - 1]; //first item is the return type + marshallers[i] = CreateMarshaller(parameterType, + MarshallerType.Argument, + parameterMetadata.MarshalAsDescriptor, + direction, + marshallers, + parameterMetadata.Index, + flags, + parameterMetadata.In, + parameterMetadata.Out, + parameterMetadata.Return); + } + + return marshallers; + } + #endregion + + + #region Marshalling Requirement Checking + private static bool IsMarshallingRequired(MarshallerKind kind) + { + switch (kind) { case MarshallerKind.Enum: case MarshallerKind.BlittableValue: @@ -381,7 +423,54 @@ namespace Internal.TypeSystem.Interop } return true; } -#endregion + + private bool IsMarshallingRequired() + { + return Out || IsMarshallingRequired(MarshallerKind); + } + + public static bool IsMarshallingRequired(MethodDesc targetMethod) + { + Debug.Assert(targetMethod.IsPInvoke); + + if (targetMethod.GetPInvokeMethodMetadata().Flags.SetLastError) + return true; + + var marshallers = GetMarshallersForMethod(targetMethod); + + for (int i = 0; i < marshallers.Length; i++) + { + if (marshallers[i].IsMarshallingRequired()) + return true; + } + return false; + } + + public static bool IsMarshallingRequired(MethodSignature methodSig, ParameterMetadata[] paramMetadata) + { + for (int i = 0, paramIndex = 0; i < methodSig.Length + 1; i++) + { + ParameterMetadata parameterMetadata = (paramIndex == paramMetadata.Length || i < paramMetadata[paramIndex].Index) ? + new ParameterMetadata(i, ParameterMetadataAttributes.None, null) : + paramMetadata[paramIndex++]; + + TypeDesc parameterType = (i == 0) ? methodSig.ReturnType : methodSig[i - 1]; //first item is the return type + + MarshallerKind marshallerKind = MarshalHelpers.GetMarshallerKind( + parameterType, + parameterMetadata.MarshalAsDescriptor, + parameterMetadata.Return, + isAnsi: true, + MarshallerType.Argument, + out MarshallerKind elementMarshallerKind); + + if (IsMarshallingRequired(marshallerKind)) + return true; + } + + return false; + } + #endregion public virtual void EmitMarshallingIL(PInvokeILCodeStreams pInvokeILCodeStreams) { @@ -1095,17 +1184,17 @@ namespace Internal.TypeSystem.Interop var lRangeCheck = emitter.NewCodeLabel(); var lLoopHeader = emitter.NewCodeLabel(); - var lNullArray = emitter.NewCodeLabel(); + var lNullArray = emitter.NewCodeLabel(); var vNativeTemp = emitter.NewLocal(NativeType); var vIndex = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32)); var vSizeOf = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.IntPtr)); var vLength = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32)); - + // Check for null array LoadManagedValue(codeStream); codeStream.Emit(ILOpcode.brfalse, lNullArray); - + // loads the number of elements EmitElementCount(codeStream, MarshalDirection.Forward); codeStream.EmitStLoc(vLength); @@ -1405,7 +1494,7 @@ namespace Internal.TypeSystem.Interop { get { - return MarshalDirection == MarshalDirection.Forward + return MarshalDirection == MarshalDirection.Forward && MarshallerType != MarshallerType.Field && !IsManagedByRef && In @@ -1529,7 +1618,7 @@ namespace Internal.TypeSystem.Interop // // ANSI marshalling. Allocate a byte array, copy characters // - + #if READYTORUN var stringToAnsi = Context.SystemModule.GetKnownType("System.StubHelpers", "AnsiBSTRMarshaler") @@ -1671,7 +1760,7 @@ namespace Internal.TypeSystem.Interop #endif codeStream.Emit(ILOpcode.newobj, emitter.NewToken(exceptionCtor)); codeStream.Emit(ILOpcode.throw_); - + // This is unreachable, but it maintains invariants about stack height prescribed by ECMA-335 codeStream.Emit(ILOpcode.ldnull); return; diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs index 757ef62..b984f8f 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/HeaderNode.cs @@ -119,7 +119,7 @@ namespace ILCompiler.DependencyAnalysis.ReadyToRun builder.EmitShort((short)(ReadyToRunHeaderConstants.CurrentMinorVersion)); // ReadyToRunHeader.Flags - builder.EmitInt(0); + builder.EmitInt((int)ReadyToRunFlag.READYTORUN_FLAG_NONSHARED_PINVOKE_STUBS); // ReadyToRunHeader.NumberOfSections ObjectDataBuilder.Reservation sectionCountReservation = builder.ReserveInt(); diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs new file mode 100644 index 0000000..8bbe425 --- /dev/null +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/MethodExtensions.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Internal.TypeSystem; + +namespace ILCompiler +{ + static class MethodExtensions + { + /// + /// Returns true if is an actual native entrypoint. + /// There's a distinction between when a method reports it's a PInvoke in the metadata + /// versus how it's treated in the compiler. For many PInvoke methods the compiler will generate + /// an IL body. The methods with an IL method body shouldn't be treated as PInvoke within the compiler. + /// + public static bool IsRawPInvoke(this MethodDesc method) + { + return method.IsPInvoke && (method is Internal.IL.Stubs.PInvokeTargetNativeMethod); + } + } +} diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index 059dcea..e9cfb44 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -140,8 +140,7 @@ namespace ILCompiler && key.IsPInvoke && _compilationModuleGroup.GeneratesPInvoke(key)) { - // TODO: enable when IL Stubs are fixed to be non-shared - // methodIL = PInvokeILEmitter.EmitIL(key); + methodIL = PInvokeILEmitter.EmitIL(key); } return new MethodILData() { Method = key, MethodIL = methodIL }; diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs index 271dcdf..a079c20 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunSingleAssemblyCompilationModuleGroup.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; - +using Internal.TypeSystem.Interop; using Debug = System.Diagnostics.Debug; namespace ILCompiler @@ -191,13 +191,19 @@ namespace ILCompiler public override bool GeneratesPInvoke(MethodDesc method) { - // PInvokes depend on details of the core library. + // PInvokes depend on details of the core library, so for now only compile them if: + // 1) We're compiling the core library module, or + // 2) We're compiling any module, and no marshalling is needed // - // We allow compiling them if both the module of the pinvoke and the core library - // are in the version bubble. + // TODO Future: consider compiling PInvokes with complex marshalling in version bubble + // mode when the core library is included in the bubble. + Debug.Assert(method is EcmaMethod); - return _versionBubbleModuleSet.Contains(((EcmaMethod)method).Module) - && _versionBubbleModuleSet.Contains(method.Context.SystemModule); + + if (((EcmaMethod)method).Module.Equals(method.Context.SystemModule)) + return true; + + return !Marshaller.IsMarshallingRequired(method); } } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/Stubs/PInvokeILEmitter.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/Stubs/PInvokeILEmitter.cs index 954abaf..bf09756 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/Stubs/PInvokeILEmitter.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/IL/Stubs/PInvokeILEmitter.cs @@ -3,7 +3,11 @@ // See the LICENSE file in the project root for more information. using System; +using System.Net; +using System.Collections.Concurrent; +using System.Collections.Generic; +using ILCompiler; using Internal.TypeSystem; using Internal.TypeSystem.Interop; using Internal.JitInterface; @@ -26,48 +30,7 @@ namespace Internal.IL.Stubs Debug.Assert(targetMethod.IsPInvoke); _targetMethod = targetMethod; _importMetadata = targetMethod.GetPInvokeMethodMetadata(); - - _marshallers = InitializeMarshallers(targetMethod, _importMetadata.Flags); - } - - private static Marshaller[] InitializeMarshallers(MethodDesc targetMethod, PInvokeFlags flags) - { - MarshalDirection direction = MarshalDirection.Forward; - MethodSignature methodSig = targetMethod.Signature; - - ParameterMetadata[] parameterMetadataArray = targetMethod.GetParameterMetadata(); - Marshaller[] marshallers = new Marshaller[methodSig.Length + 1]; - int parameterIndex = 0; - ParameterMetadata parameterMetadata; - - for (int i = 0; i < marshallers.Length; i++) - { - Debug.Assert(parameterIndex == parameterMetadataArray.Length || i <= parameterMetadataArray[parameterIndex].Index); - if (parameterIndex == parameterMetadataArray.Length || i < parameterMetadataArray[parameterIndex].Index) - { - // if we don't have metadata for the parameter, create a dummy one - parameterMetadata = new ParameterMetadata(i, ParameterMetadataAttributes.None, null); - } - else - { - Debug.Assert(i == parameterMetadataArray[parameterIndex].Index); - parameterMetadata = parameterMetadataArray[parameterIndex++]; - } - TypeDesc parameterType = (i == 0) ? methodSig.ReturnType : methodSig[i - 1]; //first item is the return type - marshallers[i] = Marshaller.CreateMarshaller(parameterType, - MarshallerType.Argument, - parameterMetadata.MarshalAsDescriptor, - direction, - marshallers, - parameterMetadata.Index, - flags, - parameterMetadata.In, - parameterMetadata.Out, - parameterMetadata.Return - ); - } - - return marshallers; + _marshallers = Marshaller.GetMarshallersForMethod(targetMethod); } private void EmitPInvokeCall(PInvokeILCodeStreams ilCodeStreams) @@ -93,18 +56,13 @@ namespace Internal.IL.Stubs nativeParameterTypes[i - 1] = _marshallers[i].NativeParameterType; } - callsiteSetupCodeStream.Emit(ILOpcode.call, emitter.NewToken( - stubHelpersType.GetKnownMethod("GetStubContext", null))); - callsiteSetupCodeStream.Emit(ILOpcode.call, emitter.NewToken( - stubHelpersType.GetKnownMethod("GetNDirectTarget", null))); - - MethodSignatureFlags unmanagedCallConv = _importMetadata.Flags.UnmanagedCallingConvention; - MethodSignature nativeSig = new MethodSignature( - _targetMethod.Signature.Flags | unmanagedCallConv, 0, nativeReturnType, + _targetMethod.Signature.Flags, 0, nativeReturnType, nativeParameterTypes); - callsiteSetupCodeStream.Emit(ILOpcode.calli, emitter.NewToken(nativeSig)); + var rawTargetMethod = new PInvokeTargetNativeMethod(_targetMethod, nativeSig); + + callsiteSetupCodeStream.Emit(ILOpcode.call, emitter.NewToken(rawTargetMethod)); // if the SetLastError flag is set in DllImport, call the PInvokeMarshal. // SaveLastWin32Error so that last error can be used later by calling @@ -133,7 +91,7 @@ namespace Internal.IL.Stubs _marshallers[0].LoadReturnValue(unmarshallingCodestream); unmarshallingCodestream.Emit(ILOpcode.ret); - return new PInvokeILStubMethodIL((ILStubMethodIL)emitter.Link(_targetMethod), IsStubRequired()); + return new PInvokeILStubMethodIL((ILStubMethodIL)emitter.Link(_targetMethod)); } public static MethodIL EmitIL(MethodDesc method) @@ -151,31 +109,15 @@ namespace Internal.IL.Stubs throw new RequiresRuntimeJitException(method); } } - - private bool IsStubRequired() - { - Debug.Assert(_targetMethod.IsPInvoke); - - if (_importMetadata.Flags.SetLastError) - { - return true; - } - - for (int i = 0; i < _marshallers.Length; i++) - { - if (_marshallers[i].IsMarshallingRequired()) - return true; - } - return false; - } } public sealed class PInvokeILStubMethodIL : ILStubMethodIL { - public bool IsStubRequired { get; } - public PInvokeILStubMethodIL(ILStubMethodIL methodIL, bool isStubRequired) : base(methodIL) + public bool IsMarshallingRequired { get; } + + public PInvokeILStubMethodIL(ILStubMethodIL methodIL) : base(methodIL) { - IsStubRequired = isStubRequired; + IsMarshallingRequired = Marshaller.IsMarshallingRequired(methodIL.OwningMethod); } } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index d4aba1f..5cd44bc 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -169,6 +169,7 @@ + diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index e44aa29..4e51eab 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -11,6 +11,7 @@ using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using Internal.IL; +using Internal.IL.Stubs; using Internal.Text; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -661,6 +662,9 @@ namespace Internal.JitInterface if (resultDef is MethodDesc) { + if (resultDef is IL.Stubs.PInvokeTargetNativeMethod rawPinvoke) + resultDef = rawPinvoke.Target; + Debug.Assert(resultDef is EcmaMethod); Debug.Assert(_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(((EcmaMethod)resultDef).OwningType)); token = (mdToken)MetadataTokens.GetToken(((EcmaMethod)resultDef).Handle); @@ -1403,6 +1407,10 @@ namespace Internal.JitInterface { nonUnboxingMethod = methodToCall.GetUnboxedMethod(); } + if (nonUnboxingMethod is IL.Stubs.PInvokeTargetNativeMethod rawPinvoke) + { + nonUnboxingMethod = rawPinvoke.Target; + } // READYTORUN: FUTURE: Direct calls if possible pResult->codePointerOrStubLookup.constLookup = CreateConstLookupToSymbol( @@ -1926,7 +1934,10 @@ namespace Internal.JitInterface private void getAddressOfPInvokeTarget(CORINFO_METHOD_STRUCT_* method, ref CORINFO_CONST_LOOKUP pLookup) { - EcmaMethod ecmaMethod = (EcmaMethod)HandleToObject(method); + MethodDesc methodDesc = HandleToObject(method); + if (methodDesc is IL.Stubs.PInvokeTargetNativeMethod rawPInvoke) + methodDesc = rawPInvoke.Target; + EcmaMethod ecmaMethod = (EcmaMethod)methodDesc; ModuleToken moduleToken = new ModuleToken(ecmaMethod.Module, ecmaMethod.Handle); MethodWithToken methodWithToken = new MethodWithToken(ecmaMethod, moduleToken, constrainedType: null); @@ -1944,13 +1955,26 @@ namespace Internal.JitInterface if (handle != null) { - EcmaMethod method = (EcmaMethod)HandleToObject(handle); - return MethodRequiresMarshaling(method); + var method = HandleToObject(handle); + if (method.IsRawPInvoke()) + { + return false; + } + + MethodIL stubIL = _compilation.GetMethodIL(method); + if (stubIL == null) + { + // This is the case of a PInvoke method that requires marshallers, which we can't use in this compilation + Debug.Assert(!_compilation.NodeFactory.CompilationModuleGroup.GeneratesPInvoke(method)); + return true; + } + + return ((PInvokeILStubMethodIL)stubIL).IsMarshallingRequired; } else { var sig = (MethodSignature)HandleToObject((IntPtr)callSiteSig->pSig); - return SignatureRequiresMarshaling(sig); + return Marshaller.IsMarshallingRequired(sig, Array.Empty()); } } @@ -1966,86 +1990,6 @@ namespace Internal.JitInterface throw new RequiresRuntimeJitException($"{MethodBeingCompiled} -> {nameof(canGetCookieForPInvokeCalliSig)}"); } - private bool MethodRequiresMarshaling(EcmaMethod method) - { - if (method.GetPInvokeMethodMetadata().Flags.SetLastError) - { - // SetLastError is handled by stub - return true; - } - - if ((method.ImplAttributes & MethodImplAttributes.PreserveSig) == 0) - { - // HRESULT swapping is handled by stub - return true; - } - - return SignatureRequiresMarshaling(method.Signature); - } - - private bool SignatureRequiresMarshaling(MethodSignature sig) - { - if (TypeRequiresMarshaling(sig.ReturnType, isReturnType: true)) - { - return true; - } - - foreach (TypeDesc argType in sig) - { - if (TypeRequiresMarshaling(argType, isReturnType: false)) - { - return true; - } - } - - return false; - } - - private bool TypeRequiresMarshaling(TypeDesc type, bool isReturnType) - { - switch (type.Category) - { - case TypeFlags.Pointer: - // TODO: custom modifiers S(NeedsCopyConstructorModifier, IsCopyConstructed) - break; - - case TypeFlags.Enum: - if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(type)) - { - return true; - } - break; - - case TypeFlags.ValueType: - if (!MarshalUtils.IsBlittableType(type)) - { - return true; - } - if (!_compilation.NodeFactory.CompilationModuleGroup.VersionsWithType(type)) - { - return true; - } - if (isReturnType && !type.IsPrimitive) - { - return true; - } - break; - - case TypeFlags.Boolean: - case TypeFlags.Char: - return true; - - default: - if (!type.IsPrimitive) - { - return true; - } - break; - } - - return false; - } - private int SizeOfPInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_PInvokeTransitionFrameSizeInPointerUnits * _compilation.NodeFactory.Target.PointerSize; } } diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj b/src/coreclr/src/tools/crossgen2/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj index 205e981..2befca9 100644 --- a/src/coreclr/src/tools/crossgen2/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj +++ b/src/coreclr/src/tools/crossgen2/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj @@ -441,6 +441,18 @@ IL\Stubs\ILEmitter.cs + + IL\Stubs\PInvokeTargetNativeMethod.cs + + + IL\Stubs\PInvokeTargetNativeMethod.Diagnostic.cs + + + IL\Stubs\PInvokeTargetNativeMethod.Mangling.cs + + + IL\Stubs\PInvokeTargetNativeMethod.Sorting.cs + TypeSystem\CodeGen\FieldDesc.Serialization.cs diff --git a/src/coreclr/src/tools/r2rdump/R2RHeader.cs b/src/coreclr/src/tools/r2rdump/R2RHeader.cs index 71ee6ca..f237e37 100644 --- a/src/coreclr/src/tools/r2rdump/R2RHeader.cs +++ b/src/coreclr/src/tools/r2rdump/R2RHeader.cs @@ -16,10 +16,10 @@ namespace R2RDump [Flags] public enum ReadyToRunFlag { - NONE = 0x00000000, - READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE = 0x00000001, - READYTORUN_FLAG_SKIP_TYPE_VALIDATION = 0x00000002, + READYTORUN_FLAG_PLATFORM_NEUTRAL_SOURCE = 0x00000001, // Set if the original IL assembly was platform-neutral + READYTORUN_FLAG_SKIP_TYPE_VALIDATION = 0x00000002, // Set of methods with native code was determined using profile data READYTORUN_FLAG_PARTIAL = 0x00000004, + READYTORUN_FLAG_CROSSGEN2_IMAGE = 0x00000008 // Set if image was compiled using crossgen2 } /// diff --git a/src/coreclr/src/vm/prestub.cpp b/src/coreclr/src/vm/prestub.cpp index 16e28b5..592df3a 100644 --- a/src/coreclr/src/vm/prestub.cpp +++ b/src/coreclr/src/vm/prestub.cpp @@ -72,6 +72,14 @@ void MethodDesc::Init() #endif +#define LOG_USING_R2R_CODE(method) LOG((LF_ZAP, LL_INFO10000, \ + "ZAP: Using R2R precompiled code" FMT_ADDR " for %s.%s sig=\"%s\" (token %x).\n", \ + DBG_ADDR(pCode), \ + m_pszDebugClassName, \ + m_pszDebugMethodName, \ + m_pszDebugMethodSignature, \ + GetMemberDef())); + //========================================================================== PCODE MethodDesc::DoBackpatch(MethodTable * pMT, MethodTable *pDispatchingMT, BOOL fFullBackPatch) @@ -352,30 +360,27 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig) if (pConfig->MayUsePrecompiledCode()) { #ifdef FEATURE_READYTORUN - // TODO: Remove IsSystem check when IL Stubs are fixed to be non-shared - if (this->IsDynamicMethod() && GetLoaderModule()->IsSystem() && MayUsePrecompiledILStub()) + if (IsDynamicMethod() && GetLoaderModule()->IsSystem() && MayUsePrecompiledILStub()) { - DynamicMethodDesc *stubMethodDesc = this->AsDynamicMethodDesc(); - if (stubMethodDesc->IsILStub() && stubMethodDesc->IsPInvokeStub()) + // Images produced using crossgen2 have non-shareable pinvoke stubs which can't be used with the IL + // stubs that the runtime generates (they take no secret parameter, and each pinvoke has a separate code) + if (GetModule()->IsReadyToRun() && !GetModule()->GetReadyToRunInfo()->HasNonShareablePInvokeStubs()) { - ILStubResolver *pStubResolver = stubMethodDesc->GetILStubResolver(); - if (pStubResolver->IsCLRToNativeInteropStub()) + DynamicMethodDesc* stubMethodDesc = this->AsDynamicMethodDesc(); + if (stubMethodDesc->IsILStub() && stubMethodDesc->IsPInvokeStub()) { - MethodDesc *pTargetMD = stubMethodDesc->GetILStubResolver()->GetStubTargetMethodDesc(); - if (pTargetMD != NULL) + ILStubResolver* pStubResolver = stubMethodDesc->GetILStubResolver(); + if (pStubResolver->IsCLRToNativeInteropStub()) { - pCode = pTargetMD->GetPrecompiledR2RCode(pConfig); - if (pCode != NULL) + MethodDesc* pTargetMD = stubMethodDesc->GetILStubResolver()->GetStubTargetMethodDesc(); + if (pTargetMD != NULL) { - LOG((LF_ZAP, LL_INFO10000, - "ZAP: Using R2R precompiled code" FMT_ADDR " for %s.%s sig=\"%s\" (token %x).\n", - DBG_ADDR(pCode), - m_pszDebugClassName, - m_pszDebugMethodName, - m_pszDebugMethodSignature, - GetMemberDef())); - - pConfig->SetNativeCode(pCode, &pCode); + pCode = pTargetMD->GetPrecompiledR2RCode(pConfig); + if (pCode != NULL) + { + LOG_USING_R2R_CODE(this); + pConfig->SetNativeCode(pCode, &pCode); + } } } } @@ -430,13 +435,7 @@ PCODE MethodDesc::GetPrecompiledCode(PrepareCodeConfig* pConfig) pCode = GetPrecompiledR2RCode(pConfig); if (pCode != NULL) { - LOG((LF_ZAP, LL_INFO10000, - "ZAP: Using R2R precompiled code" FMT_ADDR " for %s.%s sig=\"%s\" (token %x).\n", - DBG_ADDR(pCode), - m_pszDebugClassName, - m_pszDebugMethodName, - m_pszDebugMethodSignature, - GetMemberDef())); + LOG_USING_R2R_CODE(this); if (pConfig->SetNativeCode(pCode, &pCode)) { @@ -2079,7 +2078,21 @@ PCODE MethodDesc::DoPrestub(MethodTable *pDispatchingMT) } // end else if (IsIL() || IsNoMetadata()) else if (IsNDirect()) { - pCode = GetStubForInteropMethod(this); + if (GetModule()->IsReadyToRun() && GetModule()->GetReadyToRunInfo()->HasNonShareablePInvokeStubs() && MayUsePrecompiledILStub()) + { + // In crossgen2, we compile non-shareable IL stubs for pinvokes. If we can find code for such + // a stub, we'll use it directly instead and avoid emitting an IL stub. + PrepareCodeConfig config(NativeCodeVersion(this), TRUE, TRUE); + pCode = GetPrecompiledR2RCode(&config); + if (pCode != NULL) + { + LOG_USING_R2R_CODE(this); + } + } + + if (pCode == NULL) + pCode = GetStubForInteropMethod(this); + GetOrCreatePrecode(); } else if (IsFCall()) diff --git a/src/coreclr/src/vm/readytoruninfo.h b/src/coreclr/src/vm/readytoruninfo.h index c6057c7..370dca3 100644 --- a/src/coreclr/src/vm/readytoruninfo.h +++ b/src/coreclr/src/vm/readytoruninfo.h @@ -74,6 +74,12 @@ public: return m_pHeader->Flags & READYTORUN_FLAG_PARTIAL; } + BOOL HasNonShareablePInvokeStubs() + { + LIMITED_METHOD_CONTRACT; + return m_pHeader->Flags & READYTORUN_FLAG_NONSHARED_PINVOKE_STUBS; + } + PTR_PEImageLayout GetImage() { LIMITED_METHOD_CONTRACT; -- 2.7.4