From a3f539d9d3ca4f70bc7ae366e00042ba4ab30eaa Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Mon, 30 Sep 2019 22:49:32 -0700 Subject: [PATCH] [CPAOT] Generating code for hardware intrinsic (#26772) --- .../Common/JitInterface/CorInfoImpl.cs | 42 ++++--- .../ReadyToRunCodegenCompilationBuilder.cs | 1 + .../JitInterface/CorInfoImpl.ReadyToRun.cs | 116 +++++++++++++++++- 3 files changed, 140 insertions(+), 19 deletions(-) diff --git a/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs b/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs index 57e19f629a9d..058a1d182f3a 100644 --- a/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs +++ b/src/tools/crossgen2/Common/JitInterface/CorInfoImpl.cs @@ -710,11 +710,6 @@ private uint getMethodAttribsInternal(MethodDesc method) return (uint)result; } - private uint getMethodAttribs(CORINFO_METHOD_STRUCT_* ftn) - { - return getMethodAttribsInternal(HandleToObject(ftn)); - } - private void setMethodAttribs(CORINFO_METHOD_STRUCT_* ftn, CorInfoMethodRuntimeFlags attribs) { // TODO: Inlining @@ -901,6 +896,7 @@ private bool isInSIMDModule(CORINFO_CLASS_STRUCT_* classHnd) getJitFlags(ref flags, (uint)sizeof(CORJIT_FLAGS)); Debug.Assert(!_simdHelper.IsVectorOfT(type) + || ((DefType)type).InstanceFieldSize.IsIndeterminate /* This would happen in the ReadyToRun case */ || ((DefType)type).InstanceFieldSize.AsInt == GetMaxIntrinsicSIMDVectorLength(_jit, &flags)); #endif @@ -2498,33 +2494,45 @@ private static byte[] StringToUTF8(string s) return (byte*)GetPin(StringToUTF8(method.Name)); } - private byte* getMethodNameFromMetadata(CORINFO_METHOD_STRUCT_* ftn, byte** className, byte** namespaceName, byte** enclosingClassName) + private String getMethodNameFromMetadataImpl(MethodDesc method, out string className, out string namespaceName, out string enclosingClassName) { - MethodDesc method = HandleToObject(ftn); - string result = null; - string classResult = null; - string namespaceResult = null; - string enclosingResult = null; + className = null; + namespaceName = null; + enclosingClassName = null; result = method.Name; MetadataType owningType = method.OwningType as MetadataType; if (owningType != null) { - classResult = owningType.Name; - namespaceResult = owningType.Namespace; + className = owningType.Name; + namespaceName = owningType.Namespace; // Query enclosingClassName when the method is in a nested class // and get the namespace of enclosing classes (nested class's namespace is empty) var containingType = owningType.ContainingType; if (containingType != null) { - enclosingResult = containingType.Name; - namespaceResult = containingType.Namespace; + enclosingClassName = containingType.Name; + namespaceName = containingType.Namespace; } } - + + return result; + } + + private byte* getMethodNameFromMetadata(CORINFO_METHOD_STRUCT_* ftn, byte** className, byte** namespaceName, byte** enclosingClassName) + { + MethodDesc method = HandleToObject(ftn); + + string result; + string classResult; + string namespaceResult; + string enclosingResult; + + result = getMethodNameFromMetadataImpl(method, out classResult, out namespaceResult, out enclosingResult); + if (className != null) *className = classResult != null ? (byte*)GetPin(StringToUTF8(classResult)) : null; if (namespaceName != null) @@ -3024,7 +3032,6 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes) flags.Set(CorJitFlag.CORJIT_FLAG_PREJIT); flags.Set(CorJitFlag.CORJIT_FLAG_USE_PINVOKE_HELPERS); -#if !READYTORUN if (_compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.X86 || _compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.X64) { @@ -3036,7 +3043,6 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes) flags.Set(CorJitFlag.CORJIT_FLAG_USE_SSSE3); flags.Set(CorJitFlag.CORJIT_FLAG_USE_LZCNT); } -#endif if (this.MethodBeingCompiled.IsNativeCallable) flags.Set(CorJitFlag.CORJIT_FLAG_REVERSE_PINVOKE); diff --git a/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index 4e1677c07cd6..4584331ca022 100644 --- a/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -137,6 +137,7 @@ public override ICompilation ToCompilation() corJitFlags.Add(CorJitFlag.CORJIT_FLAG_PROF_REJIT_NOPS); + corJitFlags.Add(CorJitFlag.CORJIT_FLAG_FEATURE_SIMD); var jitConfig = new JitConfigProvider(corJitFlags, _ryujitOptions); return new ReadyToRunCodegenCompilation( diff --git a/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 69bde6011f2a..cfd163ffbde0 100644 --- a/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/tools/crossgen2/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -150,7 +150,16 @@ private static mdToken FindGenericMethodArgTypeSpec(EcmaModule module) private bool ShouldSkipCompilation(IMethodNode methodCodeNodeNeedingCode) { - return methodCodeNodeNeedingCode.Method.IsAggressiveOptimization; + MethodDesc method = methodCodeNodeNeedingCode.Method; + if (method.IsAggressiveOptimization) + { + return true; + } + if (HardwareIntrinsicHelpers.IsHardwareIntrinsic(method)) + { + return true; + } + return false; } public void CompileMethod(IReadyToRunMethodCodeNode methodCodeNodeNeedingCode) @@ -1154,6 +1163,109 @@ private void ceeInfoGetCallInfo( Get_CORINFO_SIG_INFO(methodToDescribe, &pResult->sig, useInstantiatingStub); } + private uint getMethodAttribs(CORINFO_METHOD_STRUCT_* ftn) + { + MethodDesc method = HandleToObject(ftn); + uint attribs = getMethodAttribsInternal(method); + attribs = FilterNamedIntrinsicMethodAttribs(attribs, method); + return attribs; + } + + private uint FilterNamedIntrinsicMethodAttribs(uint attribs, MethodDesc method) + { + bool _TARGET_X86_ = _compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.X86; + bool _TARGET_AMD64_ = _compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.X64; + bool _TARGET_ARM64_ = _compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.ARM64; + + if ((attribs & (uint)CorInfoFlag.CORINFO_FLG_JIT_INTRINSIC) != 0) + { + // Figure out which intrinsic we are dealing with. + string namespaceName; + string className; + string enclosingClassName; + string methodName = this.getMethodNameFromMetadataImpl(method, out className, out namespaceName, out enclosingClassName); + + // Is this the get_IsSupported method that checks whether intrinsic is supported? + bool fIsGetIsSupportedMethod = string.Equals(methodName, "get_IsSupported"); + bool fIsPlatformHWIntrinsic = false; + bool fIsHWIntrinsic = false; + bool fTreatAsRegularMethodCall = false; + + if (_TARGET_X86_ || _TARGET_AMD64_) + { + fIsPlatformHWIntrinsic = (namespaceName == "System.Runtime.Intrinsics.X86"); + } + else if (_TARGET_ARM64_) + { + fIsPlatformHWIntrinsic = (namespaceName == "System.Runtime.Intrinsics.Arm.Arm64"); + } + + fIsHWIntrinsic = fIsPlatformHWIntrinsic || (namespaceName == "System.Runtime.Intrinsics"); + + // By default, we want to treat the get_IsSupported method for platform specific HWIntrinsic ISAs as + // method calls. This will be modified as needed below based on what ISAs are considered baseline. + // + // We also want to treat the non-platform specific hardware intrinsics as regular method calls. This + // is because they often change the code they emit based on what ISAs are supported by the compiler, + // but we don't know what the target machine will support. + // + // Additionally, we make sure none of the hardware intrinsic method bodies get pregenerated in crossgen + // (see ShouldSkipCompilation) but get JITted instead. The JITted method will have the correct + // answer for the CPU the code is running on. + fTreatAsRegularMethodCall = (fIsGetIsSupportedMethod && fIsPlatformHWIntrinsic) || (!fIsPlatformHWIntrinsic && fIsHWIntrinsic); + + if (_TARGET_X86_ || _TARGET_AMD64_) + { + if (fIsPlatformHWIntrinsic) + { + // Simplify the comparison logic by grabbing the name of the ISA + string isaName = (enclosingClassName == null) ? className : enclosingClassName; + if ((isaName == "Sse") || (isaName == "Sse2")) + { + if ((enclosingClassName == null) || (className == "X64")) + { + // If it's anything related to Sse/Sse2, we can expand unconditionally since this is + // a baseline requirement of CoreCLR. + fTreatAsRegularMethodCall = false; + } + } + else if ((className == "Avx") || (className == "Fma") || (className == "Avx2") || (className == "Bmi1") || (className == "Bmi2")) + { + if ((enclosingClassName == null) || (string.Equals(className, "X64"))) + { + // If it is the get_IsSupported method for an ISA which requires the VEX + // encoding we want to expand unconditionally. This will force those code + // paths to be treated as dead code and dropped from the compilation. + // + // For all of the other intrinsics in an ISA which requires the VEX encoding + // we need to treat them as regular method calls. This is done because RyuJIT + // doesn't currently support emitting both VEX and non-VEX encoded instructions + // for a single method. + fTreatAsRegularMethodCall = !fIsGetIsSupportedMethod; + } + } + } + else if (namespaceName == "System") + { + if ((className == "Math") || (className == "MathF")) + { + // These are normally handled via the SSE4.1 instructions ROUNDSS/ROUNDSD. + // However, we don't know the ISAs the target machine supports so we should + // fallback to the method call implementation instead. + fTreatAsRegularMethodCall = (methodName == "Round"); + } + } + } + + if (fTreatAsRegularMethodCall) + { + // Treat as a regular method call (into a JITted method). + attribs = (attribs & ~(uint)CorInfoFlag.CORINFO_FLG_JIT_INTRINSIC) | (uint)CorInfoFlag.CORINFO_FLG_DONT_INLINE; + } + } + return attribs; + } + private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, CORINFO_CALLINFO_FLAGS flags, CORINFO_CALL_INFO* pResult) { MethodDesc methodToCall; @@ -1179,6 +1291,8 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO out callerModule, out useInstantiatingStub); + pResult->methodFlags = FilterNamedIntrinsicMethodAttribs(pResult->methodFlags, methodToCall); + if (pResult->thisTransform == CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS) { // READYTORUN: FUTURE: Optionally create boxing stub at runtime