// 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.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Text.Unicode; #if SUPPORT_JIT using Internal.Runtime.CompilerServices; #endif using Internal.IL; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; using Internal.TypeSystem.Interop; using Internal.CorConstants; using Internal.Pgo; using ILCompiler; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.Wasm; #if READYTORUN using ILCompiler.ReadyToRun.TypeSystem; using System.Reflection.Metadata.Ecma335; using ILCompiler.DependencyAnalysis.ReadyToRun; #endif using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList; #pragma warning disable IDE0060 namespace Internal.JitInterface { internal enum CompilationResult { CompilationComplete, CompilationRetryRequested } internal sealed unsafe partial class CorInfoImpl { // // Global initialization and state // internal const string JitLibrary = "clrjitilc"; #if SUPPORT_JIT private const string JitSupportLibrary = "*"; #else internal const string JitSupportLibrary = "jitinterface"; #endif private IntPtr _jit; private IntPtr _unmanagedCallbacks; // array of pointers to JIT-EE interface callbacks private ExceptionDispatchInfo _lastException; private struct PgoInstrumentationResults { public PgoInstrumentationSchema* pSchema; public uint countSchemaItems; public byte* pInstrumentationData; public HRESULT hr; } private Dictionary<MethodDesc, PgoInstrumentationResults> _pgoResults = new Dictionary<MethodDesc, PgoInstrumentationResults>(); [DllImport(JitLibrary)] private static extern IntPtr jitStartup(IntPtr host); private static class JitPointerAccessor { [DllImport(JitLibrary)] private static extern IntPtr getJit(); [DllImport(JitSupportLibrary)] private static extern CorJitResult JitProcessShutdownWork(IntPtr jit); static JitPointerAccessor() { s_jit = getJit(); if (s_jit != IntPtr.Zero) { AppDomain.CurrentDomain.ProcessExit += (_, _) => JitProcessShutdownWork(s_jit); AppDomain.CurrentDomain.UnhandledException += (_, _) => JitProcessShutdownWork(s_jit); } } public static IntPtr Get() { return s_jit; } private static readonly IntPtr s_jit; } private struct LikelyClassMethodRecord { public IntPtr handle; public uint likelihood; public LikelyClassMethodRecord(IntPtr handle, uint likelihood) { this.handle = handle; this.likelihood = likelihood; } } [DllImport(JitLibrary)] private static extern uint getLikelyClasses(LikelyClassMethodRecord* pLikelyClasses, uint maxLikelyClasses, PgoInstrumentationSchema* schema, uint countSchemaItems, byte*pInstrumentationData, int ilOffset); [DllImport(JitLibrary)] private static extern uint getLikelyMethods(LikelyClassMethodRecord* pLikelyMethods, uint maxLikelyMethods, PgoInstrumentationSchema* schema, uint countSchemaItems, byte* pInstrumentationData, int ilOffset); [DllImport(JitSupportLibrary)] private static extern IntPtr GetJitHost(IntPtr configProvider); // // Per-method initialization and state // private static CorInfoImpl GetThis(IntPtr thisHandle) { CorInfoImpl _this = Unsafe.Read<CorInfoImpl>((void*)thisHandle); Debug.Assert(_this is CorInfoImpl); return _this; } [DllImport(JitSupportLibrary)] private static extern CorJitResult JitCompileMethod(out IntPtr exception, IntPtr jit, IntPtr thisHandle, IntPtr callbacks, ref CORINFO_METHOD_INFO info, uint flags, out IntPtr nativeEntry, out uint codeSize); [DllImport(JitSupportLibrary)] private static extern IntPtr AllocException([MarshalAs(UnmanagedType.LPWStr)]string message, int messageLength); [DllImport(JitSupportLibrary)] private static extern void JitSetOs(IntPtr jit, CORINFO_OS os); private IntPtr AllocException(Exception ex) { _lastException = ExceptionDispatchInfo.Capture(ex); string exString = ex.ToString(); IntPtr nativeException = AllocException(exString, exString.Length); _nativeExceptions ??= new List<IntPtr>(); _nativeExceptions.Add(nativeException); return nativeException; } [DllImport(JitSupportLibrary)] private static extern void FreeException(IntPtr obj); [DllImport(JitSupportLibrary)] private static extern char* GetExceptionMessage(IntPtr obj); public static void Startup(CORINFO_OS os) { jitStartup(GetJitHost(JitConfigProvider.UnmanagedInstance)); JitSetOs(JitPointerAccessor.Get(), os); } public CorInfoImpl() { _jit = JitPointerAccessor.Get(); if (_jit == IntPtr.Zero) { throw new IOException("Failed to initialize JIT"); } _unmanagedCallbacks = GetUnmanagedCallbacks(); } private Logger Logger { get { return _compilation.Logger; } } private CORINFO_MODULE_STRUCT_* _methodScope; // Needed to resolve CORINFO_EH_CLAUSE tokens public static IEnumerable<PgoSchemaElem> ConvertTypeHandleHistogramsToCompactTypeHistogramFormat(PgoSchemaElem[] pgoData, CompilationModuleGroup compilationModuleGroup) { bool hasHistogram = false; foreach (var elem in pgoData) { if (elem.InstrumentationKind == PgoInstrumentationKind.HandleHistogramTypes || elem.InstrumentationKind == PgoInstrumentationKind.HandleHistogramMethods) { // found histogram hasHistogram = true; break; } } if (!hasHistogram) { foreach (var elem in pgoData) { yield return elem; } } else { int currentObjectIndex = 0x1000000; // This needs to be a somewhat large non-zero number, so that the jit does not confuse it with NULL, or any other special value. Dictionary<object, IntPtr> objectToHandle = new Dictionary<object, IntPtr>(); Dictionary<IntPtr, object> handleToObject = new Dictionary<IntPtr, object>(); MemoryStream memoryStreamInstrumentationData = new MemoryStream(); ComputeJitPgoInstrumentationSchema(LocalObjectToHandle, pgoData, out var nativeSchema, memoryStreamInstrumentationData); var instrumentationData = memoryStreamInstrumentationData.ToArray(); for (int i = 0; i < pgoData.Length; i++) { if ((i + 1 < pgoData.Length) && (pgoData[i].InstrumentationKind == PgoInstrumentationKind.HandleHistogramIntCount || pgoData[i].InstrumentationKind == PgoInstrumentationKind.HandleHistogramLongCount) && (pgoData[i + 1].InstrumentationKind == PgoInstrumentationKind.HandleHistogramTypes || pgoData[i + 1].InstrumentationKind == PgoInstrumentationKind.HandleHistogramMethods)) { PgoSchemaElem? newElem = ComputeLikelyClassMethod(i, handleToObject, nativeSchema, instrumentationData, compilationModuleGroup); if (newElem.HasValue) { yield return newElem.Value; } i++; // The histogram is two entries long, so skip an extra entry continue; } yield return pgoData[i]; } IntPtr LocalObjectToHandle(object input) { if (objectToHandle.TryGetValue(input, out var result)) { return result; } result = new IntPtr(currentObjectIndex++); objectToHandle.Add(input, result); handleToObject.Add(result, input); return result; } } } private static PgoSchemaElem? ComputeLikelyClassMethod(int index, Dictionary<IntPtr, object> handleToObject, PgoInstrumentationSchema[] nativeSchema, byte[] instrumentationData, CompilationModuleGroup compilationModuleGroup) { // getLikelyClasses will use two entries from the native schema table. There must be at least two present to avoid overruning the buffer if (index > (nativeSchema.Length - 2)) return null; bool isType = nativeSchema[index + 1].InstrumentationKind == PgoInstrumentationKind.HandleHistogramTypes; fixed (PgoInstrumentationSchema* pSchema = &nativeSchema[index]) { fixed (byte* pInstrumentationData = &instrumentationData[0]) { // We're going to store only the most popular type/method to reduce size of the profile LikelyClassMethodRecord* likelyClassMethods = stackalloc LikelyClassMethodRecord[1]; uint numberOfRecords; if (isType) { numberOfRecords = getLikelyClasses(likelyClassMethods, 1, pSchema, 2, pInstrumentationData, nativeSchema[index].ILOffset); } else { numberOfRecords = getLikelyMethods(likelyClassMethods, 1, pSchema, 2, pInstrumentationData, nativeSchema[index].ILOffset); } if (numberOfRecords > 0) { TypeSystemEntityOrUnknown[] newData = null; if (isType) { TypeDesc type = (TypeDesc)handleToObject[likelyClassMethods->handle]; #if READYTORUN if (compilationModuleGroup.VersionsWithType(type)) #endif { newData = new[] { new TypeSystemEntityOrUnknown(type) }; } } else { MethodDesc method = (MethodDesc)handleToObject[likelyClassMethods->handle]; #if READYTORUN if (compilationModuleGroup.VersionsWithMethodBody(method)) #endif { newData = new[] { new TypeSystemEntityOrUnknown(method) }; } } if (newData != null) { PgoSchemaElem likelyClassElem = default(PgoSchemaElem); likelyClassElem.InstrumentationKind = isType ? PgoInstrumentationKind.GetLikelyClass : PgoInstrumentationKind.GetLikelyMethod; likelyClassElem.ILOffset = nativeSchema[index].ILOffset; likelyClassElem.Count = 1; likelyClassElem.Other = (int)(likelyClassMethods->likelihood | (numberOfRecords << 8)); likelyClassElem.DataObject = newData; return likelyClassElem; } } } } return null; } private CompilationResult CompileMethodInternal(IMethodNode methodCodeNodeNeedingCode, MethodIL methodIL) { // methodIL must not be null if (methodIL == null) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, MethodBeingCompiled); } CORINFO_METHOD_INFO methodInfo; Get_CORINFO_METHOD_INFO(MethodBeingCompiled, methodIL, &methodInfo); _methodScope = methodInfo.scope; #if !READYTORUN SetDebugInformation(methodCodeNodeNeedingCode, methodIL); #endif CorInfoImpl _this = this; IntPtr exception; IntPtr nativeEntry; uint codeSize; var result = JitCompileMethod(out exception, _jit, (IntPtr)(&_this), _unmanagedCallbacks, ref methodInfo, (uint)CorJitFlag.CORJIT_FLAG_CALL_GETJITFLAGS, out nativeEntry, out codeSize); if (exception != IntPtr.Zero) { if (_lastException != null) { // If we captured a managed exception, rethrow that. // TODO: might not actually be the real reason. It could be e.g. a JIT failure/bad IL that followed // an inlining attempt with a type system problem in it... #if SUPPORT_JIT _lastException.Throw(); #else if (_lastException.SourceException is TypeSystemException) { // Type system exceptions can be turned into code that throws the exception at runtime. _lastException.Throw(); } #if READYTORUN else if (_lastException.SourceException is RequiresRuntimeJitException) { // Runtime JIT requirement is not a cause for failure, we just mustn't JIT a particular method _lastException.Throw(); } #endif else { // This is just a bug somewhere. throw new CodeGenerationFailedException(_methodCodeNode.Method, _lastException.SourceException); } #endif } // This is a failure we don't know much about. char* szMessage = GetExceptionMessage(exception); string message = szMessage != null ? new string(szMessage) : "JIT Exception"; throw new Exception(message); } if (result == CorJitResult.CORJIT_BADCODE) { ThrowHelper.ThrowInvalidProgramException(); } if (result == CorJitResult.CORJIT_IMPLLIMITATION || result == CorJitResult.CORJIT_R2R_UNSUPPORTED) { #if READYTORUN throw new RequiresRuntimeJitException("JIT implementation limitation"); #else ThrowHelper.ThrowInvalidProgramException(); #endif } if (result == CorJitResult.CORJIT_OUTOFMEM) { throw new OutOfMemoryException(); } if (result != CorJitResult.CORJIT_OK) { #if SUPPORT_JIT // FailFast? throw new Exception("JIT failed"); #else throw new CodeGenerationFailedException(_methodCodeNode.Method); #endif } if (codeSize < _code.Length) { if (_compilation.TypeSystemContext.Target.Architecture != TargetArchitecture.ARM64 && _compilation.TypeSystemContext.Target.Architecture != TargetArchitecture.LoongArch64 && _compilation.TypeSystemContext.Target.Architecture != TargetArchitecture.RiscV64) { // For xarch/arm32/LoongArch64/RiscV64, the generated code is sometimes smaller than the memory allocated. // In that case, trim the codeBlock to the actual value. // // For arm64, the allocation request of `hotCodeSize` also includes the roData size // while the `codeSize` returned just contains the size of the native code. As such, // there is guarantee that for armarch, (codeSize == _code.Length) is always true. // // Currently, hot/cold splitting is not done and hence `codeSize` just includes the size of // hotCode. Once hot/cold splitting is done, need to trim respective `_code` or `_coldCode` // accordingly. Debug.Assert(codeSize != 0); Array.Resize(ref _code, (int)codeSize); } } CompilationResult compilationCompleteBehavior = CompilationResult.CompilationComplete; DetermineIfCompilationShouldBeRetried(ref compilationCompleteBehavior); if (compilationCompleteBehavior == CompilationResult.CompilationRetryRequested) return compilationCompleteBehavior; PublishCode(); PublishROData(); PublishRWData(); return CompilationResult.CompilationComplete; } partial void DetermineIfCompilationShouldBeRetried(ref CompilationResult result); private void PublishCode() { var relocs = _codeRelocs.ToArray(); Array.Sort(relocs, (x, y) => (x.Offset - y.Offset)); int alignment = JitConfigProvider.Instance.HasFlag(CorJitFlag.CORJIT_FLAG_SIZE_OPT) && !JitConfigProvider.Instance.HasFlag(CorJitFlag.CORJIT_FLAG_ENABLE_CFG) ? _compilation.NodeFactory.Target.MinimumFunctionAlignment : _compilation.NodeFactory.Target.OptimumFunctionAlignment; alignment = Math.Max(alignment, _codeAlignment); var objectData = new ObjectNode.ObjectData(_code, relocs, alignment, new ISymbolDefinitionNode[] { _methodCodeNode }); ObjectNode.ObjectData ehInfo = _ehClauses != null ? EncodeEHInfo() : null; DebugEHClauseInfo[] debugEHClauseInfos = null; if (_ehClauses != null) { debugEHClauseInfos = new DebugEHClauseInfo[_ehClauses.Length]; for (int i = 0; i < _ehClauses.Length; i++) { var clause = _ehClauses[i]; // clause.TryLength returned by the JIT is actually end offset... // https://github.com/dotnet/runtime/issues/5282 // We subtract offset from "length" to get the actual length. Debug.Assert(clause.TryLength >= clause.TryOffset); Debug.Assert(clause.HandlerLength >= clause.HandlerOffset); debugEHClauseInfos[i] = new DebugEHClauseInfo(clause.TryOffset, clause.TryLength - clause.TryOffset, clause.HandlerOffset, clause.HandlerLength - clause.HandlerOffset); } } _methodCodeNode.SetCode(objectData); #if READYTORUN if (_methodColdCodeNode != null) { var relocs2 = _coldCodeRelocs.ToArray(); Array.Sort(relocs2, (x, y) => (x.Offset - y.Offset)); var coldObjectData = new ObjectNode.ObjectData(_coldCode, relocs2, alignment, new ISymbolDefinitionNode[] { _methodColdCodeNode }); _methodColdCodeNode.SetCode(coldObjectData); _methodCodeNode.ColdCodeNode = _methodColdCodeNode; } #endif _methodCodeNode.InitializeFrameInfos(_frameInfos); #if READYTORUN _methodCodeNode.InitializeColdFrameInfos(_coldFrameInfos); #endif _methodCodeNode.InitializeDebugEHClauseInfos(debugEHClauseInfos); _methodCodeNode.InitializeGCInfo(_gcInfo); _methodCodeNode.InitializeEHInfo(ehInfo); _methodCodeNode.InitializeDebugLocInfos(_debugLocInfos); _methodCodeNode.InitializeDebugVarInfos(_debugVarInfos); #if READYTORUN MethodDesc[] inlineeArray; if (_inlinedMethods != null) { inlineeArray = new MethodDesc[_inlinedMethods.Count]; _inlinedMethods.CopyTo(inlineeArray); Array.Sort(inlineeArray, TypeSystemComparer.Instance.Compare); } else { inlineeArray = Array.Empty<MethodDesc>(); } _methodCodeNode.InitializeInliningInfo(inlineeArray, _compilation.NodeFactory); // Detect cases where the instruction set support used is a superset of the baseline instruction set specification var baselineSupport = _compilation.InstructionSetSupport; bool needPerMethodInstructionSetFixup = false; foreach (var instructionSet in _actualInstructionSetSupported) { if (!baselineSupport.IsInstructionSetSupported(instructionSet)) { needPerMethodInstructionSetFixup = true; } } foreach (var instructionSet in _actualInstructionSetUnsupported) { if (!baselineSupport.IsInstructionSetExplicitlyUnsupported(instructionSet)) { needPerMethodInstructionSetFixup = true; } } if (needPerMethodInstructionSetFixup) { TargetArchitecture architecture = _compilation.TypeSystemContext.Target.Architecture; _actualInstructionSetSupported.ExpandInstructionSetByImplication(architecture); _actualInstructionSetUnsupported.ExpandInstructionSetByReverseImplication(architecture); _actualInstructionSetUnsupported.Set64BitInstructionSetVariants(architecture); InstructionSetSupport actualSupport = new InstructionSetSupport(_actualInstructionSetSupported, _actualInstructionSetUnsupported, architecture); var node = _compilation.SymbolNodeFactory.PerMethodInstructionSetSupportFixup(actualSupport); AddPrecodeFixup(node); } Debug.Assert(_stashedPrecodeFixups.Count == 0); if (_precodeFixups != null) { HashSet<ISymbolNode> computedNodes = new HashSet<ISymbolNode>(); foreach (var fixup in _precodeFixups) { if (computedNodes.Add(fixup)) { if (fixup is IMethodNode methodNode) { try { _compilation.NodeFactory.DetectGenericCycles(_methodCodeNode.Method, methodNode.Method); } catch (TypeLoadException) { throw new RequiresRuntimeJitException("Requires runtime JIT - potential generic cycle detected"); } } _methodCodeNode.Fixups.Add(fixup); } } } if (_synthesizedPgoDependencies != null) { Debug.Assert(_compilation.NodeFactory.InstrumentationDataTable != null, "Expected InstrumentationDataTable to be non-null with synthesized PGO data to embed"); _compilation.NodeFactory.InstrumentationDataTable.EmbedSynthesizedPgoDataForMethods(ref _additionalDependencies, _synthesizedPgoDependencies); } #else var methodIL = (MethodIL)HandleToObject((void*)_methodScope); CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _additionalDependencies, _compilation.NodeFactory, MethodBeingCompiled, methodIL); _methodCodeNode.InitializeDebugInfo(_debugInfo); LocalVariableDefinition[] locals = methodIL.GetLocals(); TypeDesc[] localTypes = new TypeDesc[locals.Length]; for (int i = 0; i < localTypes.Length; i++) localTypes[i] = locals[i].Type; _methodCodeNode.InitializeLocalTypes(localTypes); #endif _methodCodeNode.InitializeNonRelocationDependencies(_additionalDependencies); } private void PublishROData() { if (_roDataBlob == null) { return; } var relocs = _roDataRelocs.ToArray(); Array.Sort(relocs, (x, y) => (x.Offset - y.Offset)); var objectData = new ObjectNode.ObjectData(_roData, relocs, _roDataAlignment, new ISymbolDefinitionNode[] { _roDataBlob }); _roDataBlob.InitializeData(objectData); } private void PublishRWData() { if (_rwDataBlob == null) { return; } var relocs = _rwDataRelocs.ToArray(); Array.Sort(relocs, (x, y) => (x.Offset - y.Offset)); var objectData = new ObjectNode.ObjectData(_rwData, relocs, _rwDataAlignment, new ISymbolDefinitionNode[] { _rwDataBlob }); _rwDataBlob.InitializeData(objectData); } private MethodDesc MethodBeingCompiled { get { return _methodCodeNode.Method; } } private int PointerSize { get { return _compilation.TypeSystemContext.Target.PointerSize; } } private Dictionary<object, GCHandle> _pins = new Dictionary<object, GCHandle>(); private IntPtr GetPin(object obj) { GCHandle handle; if (!_pins.TryGetValue(obj, out handle)) { handle = GCHandle.Alloc(obj, GCHandleType.Pinned); _pins.Add(obj, handle); } return handle.AddrOfPinnedObject(); } private List<IntPtr> _nativeExceptions; private void CompileMethodCleanup() { foreach (var pin in _pins) pin.Value.Free(); _pins.Clear(); if (_nativeExceptions != null) { foreach (IntPtr ex in _nativeExceptions) FreeException(ex); _nativeExceptions = null; } _methodCodeNode = null; #if READYTORUN _methodColdCodeNode = null; #endif _code = null; _coldCode = null; _roData = null; _roDataBlob = null; _rwData = null; _rwDataBlob = null; _codeRelocs = default(ArrayBuilder<Relocation>); _roDataRelocs = default(ArrayBuilder<Relocation>); _rwDataRelocs = default(ArrayBuilder<Relocation>); #if READYTORUN _coldCodeRelocs = default(ArrayBuilder<Relocation>); #endif _numFrameInfos = 0; _usedFrameInfos = 0; _frameInfos = null; #if READYTORUN _numColdFrameInfos = 0; _usedColdFrameInfos = 0; _coldFrameInfos = null; #endif _gcInfo = null; _ehClauses = null; _additionalDependencies = null; #if !READYTORUN _debugInfo = null; #endif _debugLocInfos = null; _debugVarInfos = null; _lastException = null; #if READYTORUN _inlinedMethods = null; _actualInstructionSetSupported = default(InstructionSetFlags); _actualInstructionSetUnsupported = default(InstructionSetFlags); _precodeFixups = null; _stashedPrecodeFixups.Clear(); _stashedInlinedMethods.Clear(); _ilBodiesNeeded = null; _synthesizedPgoDependencies = null; #endif _instantiationToJitVisibleInstantiation = null; _pgoResults.Clear(); // We need to clear out this cache because the next compilation could actually come up // with a different MethodIL for the same MethodDesc. This happens when we need to replace // a MethodIL with a throw helper. _methodILScopeToHandle.Clear(); } private Dictionary<object, IntPtr> _objectToHandle = new Dictionary<object, IntPtr>(new JitObjectComparer()); private Dictionary<MethodDesc, IntPtr> _methodILScopeToHandle = new Dictionary<MethodDesc, IntPtr>(new JitObjectComparer()); private List<object> _handleToObject = new List<object>(); private const int handleMultiplier = 8; private const int handleBase = 0x420000; #if DEBUG private static readonly IntPtr s_handleHighBitSet = (sizeof(IntPtr) == 4) ? new IntPtr(0x40000000) : new IntPtr(0x4000000000000000); #endif private IntPtr ObjectToHandle(object obj) { // MethodILScopes need to go through ObjectToHandle(MethodILScope methodIL). Debug.Assert(obj is not MethodILScope); return ObjectToHandleUnchecked(obj); } private IntPtr ObjectToHandleUnchecked(object obj) { // SuperPMI relies on the handle returned from this function being stable for the lifetime of the crossgen2 process // If handle deletion is implemented, please update SuperPMI IntPtr handle; if (!_objectToHandle.TryGetValue(obj, out handle)) { handle = (IntPtr)(handleMultiplier * _handleToObject.Count + handleBase); #if DEBUG handle = new IntPtr((long)s_handleHighBitSet | (long)handle); #endif _handleToObject.Add(obj); _objectToHandle.Add(obj, handle); } return handle; } private object HandleToObject(void* handle) { Debug.Assert(handle != null); #if DEBUG handle = (void*)(~s_handleHighBitSet & (nint)handle); #endif int index = ((int)handle - handleBase) / handleMultiplier; return _handleToObject[index]; } private MethodDesc HandleToObject(CORINFO_METHOD_STRUCT_* method) => (MethodDesc)HandleToObject((void*)method); private CORINFO_METHOD_STRUCT_* ObjectToHandle(MethodDesc method) => (CORINFO_METHOD_STRUCT_*)ObjectToHandle((object)method); private TypeDesc HandleToObject(CORINFO_CLASS_STRUCT_* type) => (TypeDesc)HandleToObject((void*)type); private CORINFO_CLASS_STRUCT_* ObjectToHandle(TypeDesc type) => (CORINFO_CLASS_STRUCT_*)ObjectToHandle((object)type); private FieldDesc HandleToObject(CORINFO_FIELD_STRUCT_* field) => (FieldDesc)HandleToObject((void*)field); private CORINFO_FIELD_STRUCT_* ObjectToHandle(FieldDesc field) => (CORINFO_FIELD_STRUCT_*)ObjectToHandle((object)field); private MethodILScope HandleToObject(CORINFO_MODULE_STRUCT_* module) => (MethodIL)HandleToObject((void*)module); private MethodSignature HandleToObject(MethodSignatureInfo* method) => (MethodSignature)HandleToObject((void*)method); private MethodSignatureInfo* ObjectToHandle(MethodSignature method) => (MethodSignatureInfo*)ObjectToHandle((object)method); private CORINFO_MODULE_STRUCT_* ObjectToHandle(MethodILScope methodIL) { // RyuJIT requires CORINFO_MODULE_STRUCT to be unique. MethodILScope might not be unique // due to ILProvider cache purging. See https://github.com/dotnet/runtime/issues/93843. MethodDesc owningMethod = methodIL.OwningMethod; if (!_methodILScopeToHandle.TryGetValue(owningMethod, out IntPtr handle)) _methodILScopeToHandle[owningMethod] = handle = ObjectToHandleUnchecked((object)methodIL); return (CORINFO_MODULE_STRUCT_*)handle; } private bool Get_CORINFO_METHOD_INFO(MethodDesc method, MethodIL methodIL, CORINFO_METHOD_INFO* methodInfo) { if (methodIL == null) { *methodInfo = default(CORINFO_METHOD_INFO); return false; } methodInfo->ftn = ObjectToHandle(method); methodInfo->scope = ObjectToHandle(methodIL); var ilCode = methodIL.GetILBytes(); methodInfo->ILCode = (byte*)GetPin(ilCode); methodInfo->ILCodeSize = (uint)ilCode.Length; methodInfo->maxStack = (uint)methodIL.MaxStack; var exceptionRegions = methodIL.GetExceptionRegions(); methodInfo->EHcount = (uint)exceptionRegions.Length; methodInfo->options = methodIL.IsInitLocals ? CorInfoOptions.CORINFO_OPT_INIT_LOCALS : (CorInfoOptions)0; if (method.AcquiresInstMethodTableFromThis()) { methodInfo->options |= CorInfoOptions.CORINFO_GENERICS_CTXT_FROM_THIS; } else if (method.RequiresInstMethodDescArg()) { methodInfo->options |= CorInfoOptions.CORINFO_GENERICS_CTXT_FROM_METHODDESC; } else if (method.RequiresInstMethodTableArg()) { methodInfo->options |= CorInfoOptions.CORINFO_GENERICS_CTXT_FROM_METHODTABLE; } // Indicate this is an async method that requires save and restore // of async contexts. Regular user implemented runtime async methods // require this behavior, but thunks should be transparent and should not // come with this behavior. if (method.IsAsyncVariant() && method.IsAsync) { methodInfo->options |= CorInfoOptions.CORINFO_ASYNC_SAVE_CONTEXTS; } methodInfo->regionKind = CorInfoRegionKind.CORINFO_REGION_NONE; Get_CORINFO_SIG_INFO(method, sig: &methodInfo->args, methodIL); Get_CORINFO_SIG_INFO(methodIL.GetLocals(), &methodInfo->locals); return true; } private Dictionary<Instantiation, IntPtr[]> _instantiationToJitVisibleInstantiation; private CORINFO_CLASS_STRUCT_** GetJitInstantiation(Instantiation inst) { IntPtr [] jitVisibleInstantiation; _instantiationToJitVisibleInstantiation ??= new Dictionary<Instantiation, IntPtr[]>(); if (!_instantiationToJitVisibleInstantiation.TryGetValue(inst, out jitVisibleInstantiation)) { jitVisibleInstantiation = new IntPtr[inst.Length]; for (int i = 0; i < inst.Length; i++) jitVisibleInstantiation[i] = (IntPtr)ObjectToHandle(inst[i]); _instantiationToJitVisibleInstantiation.Add(inst, jitVisibleInstantiation); } return (CORINFO_CLASS_STRUCT_**)GetPin(jitVisibleInstantiation); } private void Get_CORINFO_SIG_INFO(MethodDesc method, CORINFO_SIG_INFO* sig, MethodILScope scope, bool suppressHiddenArgument = false) { Get_CORINFO_SIG_INFO(method.Signature, sig, scope); if (method.IsAsyncCall()) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_ASYNCCALL; // Does the method have a hidden parameter? bool hasHiddenParameter = !suppressHiddenArgument && method.RequiresInstArg(); if (method.IsIntrinsic) { // Some intrinsics will beg to differ about the hasHiddenParameter decision if (method.IsArrayAddressMethod()) hasHiddenParameter = true; } if (hasHiddenParameter) { sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_PARAMTYPE; } Instantiation owningTypeInst = method.OwningType.Instantiation; sig->sigInst.classInstCount = (uint)owningTypeInst.Length; if (owningTypeInst.Length != 0) { sig->sigInst.classInst = GetJitInstantiation(owningTypeInst); } sig->sigInst.methInstCount = (uint)method.Instantiation.Length; if (method.Instantiation.Length != 0) { sig->sigInst.methInst = GetJitInstantiation(method.Instantiation); } } private void Get_CORINFO_SIG_INFO(MethodSignature signature, CORINFO_SIG_INFO* sig, MethodILScope scope) { sig->callConv = (CorInfoCallConv)(signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask); // Varargs are not supported in .NET Core if (sig->callConv == CorInfoCallConv.CORINFO_CALLCONV_VARARG) ThrowHelper.ThrowBadImageFormatException(); if (!signature.IsStatic) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_HASTHIS; if (signature.IsExplicitThis) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_EXPLICITTHIS; if (signature.GenericParameterCount != 0) sig->callConv |= CorInfoCallConv.CORINFO_CALLCONV_GENERIC; TypeDesc returnType = signature.ReturnType; CorInfoType corInfoRetType = asCorInfoType(signature.ReturnType, &sig->retTypeClass); sig->_retType = (byte)corInfoRetType; sig->retTypeSigClass = ObjectToHandle(signature.ReturnType); #if READYTORUN ValidateSafetyOfUsingTypeEquivalenceOfType(signature.ReturnType); #endif sig->flags = 0; // used by IL stubs code sig->numArgs = (ushort)signature.Length; sig->args = (CORINFO_ARG_LIST_STRUCT_*)0; // CORINFO_ARG_LIST_STRUCT_ is argument index sig->sigInst.classInst = null; // Not used by the JIT sig->sigInst.classInstCount = 0; // Not used by the JIT sig->sigInst.methInst = null; sig->sigInst.methInstCount = (uint)signature.GenericParameterCount; sig->pSig = null; sig->cbSig = 0; // Not used by the JIT sig->methodSignature = ObjectToHandle(signature); sig->scope = scope is not null ? ObjectToHandle(scope) : null; // scope can be null for internal calls and COM methods. sig->token = 0; // Not used by the JIT } private void Get_CORINFO_SIG_INFO(LocalVariableDefinition[] locals, CORINFO_SIG_INFO* sig) { sig->callConv = CorInfoCallConv.CORINFO_CALLCONV_DEFAULT; sig->_retType = (byte)CorInfoType.CORINFO_TYPE_VOID; sig->retTypeClass = null; sig->retTypeSigClass = null; sig->flags = CorInfoSigInfoFlags.CORINFO_SIGFLAG_IS_LOCAL_SIG; sig->numArgs = (ushort)locals.Length; sig->sigInst.classInst = null; sig->sigInst.classInstCount = 0; sig->sigInst.methInst = null; sig->sigInst.methInstCount = 0; sig->args = (CORINFO_ARG_LIST_STRUCT_*)0; // CORINFO_ARG_LIST_STRUCT_ is argument index sig->pSig = null; sig->cbSig = 0; // Not used by the JIT sig->methodSignature = (MethodSignatureInfo*)ObjectToHandle(locals); sig->scope = null; // Not used by the JIT sig->token = 0; // Not used by the JIT } private CorInfoType asCorInfoType(TypeDesc type) { return asCorInfoType(type, out _); } private CorInfoType asCorInfoType(TypeDesc type, out TypeDesc typeIfNotPrimitive) { if (type.IsEnum) { type = type.UnderlyingType; } if (type.IsPrimitive) { typeIfNotPrimitive = null; Debug.Assert((CorInfoType)TypeFlags.Void == CorInfoType.CORINFO_TYPE_VOID); Debug.Assert((CorInfoType)TypeFlags.Double == CorInfoType.CORINFO_TYPE_DOUBLE); return (CorInfoType)type.Category; } if (type.IsPointer || type.IsFunctionPointer) { typeIfNotPrimitive = null; return CorInfoType.CORINFO_TYPE_PTR; } typeIfNotPrimitive = type; if (type.IsByRef) { return CorInfoType.CORINFO_TYPE_BYREF; } if (type.IsValueType) { if (_compilation.TypeSystemContext.Target.Architecture == TargetArchitecture.X86) { LayoutInt elementSize = type.GetElementSize(); #if READYTORUN if (elementSize.IsIndeterminate) { throw new RequiresRuntimeJitException(type); } #endif } return CorInfoType.CORINFO_TYPE_VALUECLASS; } return CorInfoType.CORINFO_TYPE_CLASS; } private CorInfoType asCorInfoType(TypeDesc type, CORINFO_CLASS_STRUCT_** structType) { var corInfoType = asCorInfoType(type, out TypeDesc typeIfNotPrimitive); *structType = (typeIfNotPrimitive != null) ? ObjectToHandle(typeIfNotPrimitive) : null; return corInfoType; } private CORINFO_CONTEXT_STRUCT* contextFromMethod(MethodDesc method) { return (CORINFO_CONTEXT_STRUCT*)(((nuint)ObjectToHandle(method)) | (nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_METHOD); } private CORINFO_CONTEXT_STRUCT* contextFromType(TypeDesc type) { return (CORINFO_CONTEXT_STRUCT*)(((nuint)ObjectToHandle(type)) | (nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_CLASS); } private static CORINFO_CONTEXT_STRUCT* contextFromMethodBeingCompiled() { return (CORINFO_CONTEXT_STRUCT*)1; } private MethodDesc methodFromContext(CORINFO_CONTEXT_STRUCT* contextStruct) { if (contextStruct == contextFromMethodBeingCompiled()) { return MethodBeingCompiled; } if (((nuint)contextStruct & (nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_MASK) == (nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_CLASS) { return null; } else { return HandleToObject((CORINFO_METHOD_STRUCT_*)((nuint)contextStruct & ~(nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_MASK)); } } private TypeDesc typeFromContext(CORINFO_CONTEXT_STRUCT* contextStruct) { if (contextStruct == contextFromMethodBeingCompiled()) { return MethodBeingCompiled.OwningType; } if (((nuint)contextStruct & (nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_MASK) == (nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_CLASS) { return HandleToObject((CORINFO_CLASS_STRUCT_*)((nuint)contextStruct & ~(nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_MASK)); } else { return HandleToObject((CORINFO_METHOD_STRUCT_*)((nuint)contextStruct & ~(nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_MASK)).OwningType; } } private TypeSystemEntity entityFromContext(CORINFO_CONTEXT_STRUCT* contextStruct) { if (contextStruct == contextFromMethodBeingCompiled()) { return MethodBeingCompiled.HasInstantiation ? (TypeSystemEntity)MethodBeingCompiled: (TypeSystemEntity)MethodBeingCompiled.OwningType; } return (TypeSystemEntity)HandleToObject((void*)((nuint)contextStruct & ~(nuint)CorInfoContextFlags.CORINFO_CONTEXTFLAGS_MASK)); } private bool isIntrinsic(CORINFO_METHOD_STRUCT_* ftn) { MethodDesc method = HandleToObject(ftn); return method.IsIntrinsic || HardwareIntrinsicHelpers.IsHardwareIntrinsic(method); } private uint getMethodAttribsInternal(MethodDesc method) { CorInfoFlag result = 0; if (method.Signature.IsStatic) result |= CorInfoFlag.CORINFO_FLG_STATIC; if (method.IsSynchronized) result |= CorInfoFlag.CORINFO_FLG_SYNCH; if (method.IsIntrinsic) result |= CorInfoFlag.CORINFO_FLG_INTRINSIC; if (method.IsVirtual) { result |= CorInfoFlag.CORINFO_FLG_VIRTUAL; // The JIT only cares about the sealed flag if the method is virtual, or if // it is a delegate. // method or class might have the final bit if (method.IsUnboxingThunk()) { if (_compilation.IsEffectivelySealed(method.GetUnboxedMethod())) result |= CorInfoFlag.CORINFO_FLG_FINAL; } else { if (_compilation.IsEffectivelySealed(method)) result |= CorInfoFlag.CORINFO_FLG_FINAL; } } if (method.IsAbstract) result |= CorInfoFlag.CORINFO_FLG_ABSTRACT; if (method.IsConstructor || method.IsStaticConstructor) result |= CorInfoFlag.CORINFO_FLG_CONSTRUCTOR; // // See if we need to embed a .cctor call at the head of the // method body. // if (method.IsSharedByGenericInstantiations) result |= CorInfoFlag.CORINFO_FLG_SHAREDINST; if (method.IsPInvoke) result |= CorInfoFlag.CORINFO_FLG_PINVOKE; #if READYTORUN if (method.RequireSecObject) { result |= CorInfoFlag.CORINFO_FLG_DONT_INLINE_CALLER; } #endif if (method.IsAggressiveOptimization) { result |= CorInfoFlag.CORINFO_FLG_AGGRESSIVE_OPT; } // TODO: Cache inlining hits // Check for an inlining directive. if (method.IsNoInlining || method.IsNoOptimization) { // NoOptimization implies NoInlining. result |= CorInfoFlag.CORINFO_FLG_DONT_INLINE; } else if (method.IsAggressiveInlining) { result |= CorInfoFlag.CORINFO_FLG_FORCEINLINE; } if (method.OwningType.IsDelegate && method.Name.SequenceEqual("Invoke"u8)) { // This is now used to emit efficient invoke code for any delegate invoke, // including multicast. result |= CorInfoFlag.CORINFO_FLG_DELEGATE_INVOKE; // RyuJIT special cases this method; it would assert if it's not final // and we might not have set the bit in the code above. result |= CorInfoFlag.CORINFO_FLG_FINAL; } #if READYTORUN // Check for SIMD intrinsics if (method.Context.Target.MaximumSimdVectorLength == SimdVectorLength.None) { DefType owningDefType = method.OwningType as DefType; if (owningDefType != null && VectorOfTFieldLayoutAlgorithm.IsVectorOfTType(owningDefType)) { throw new RequiresRuntimeJitException("This function is using SIMD intrinsics, their size is machine specific"); } } #endif // Check for hardware intrinsics if (HardwareIntrinsicHelpers.IsHardwareIntrinsic(method)) { result |= CorInfoFlag.CORINFO_FLG_INTRINSIC; } // Internal calls typically turn into fcalls that do not always // probe for GC. Be conservative here and always let JIT know that // this method may not do GC checks so the JIT might need to make // callers fully interruptible. if (method.IsInternalCall) { result |= CorInfoFlag.CORINFO_FLG_NOGCCHECK; } return (uint)result; } #pragma warning disable CA1822 // Mark members as static private void setMethodAttribs(CORINFO_METHOD_STRUCT_* ftn, CorInfoMethodRuntimeFlags attribs) #pragma warning restore CA1822 // Mark members as static { // TODO: Inlining } private void getMethodSig(CORINFO_METHOD_STRUCT_* ftn, CORINFO_SIG_INFO* sig, CORINFO_CLASS_STRUCT_* memberParent) { MethodDesc method = HandleToObject(ftn); // There might be a more concrete parent type specified - this can happen when inlining. if (memberParent != null) { TypeDesc type = HandleToObject(memberParent); // Typically, the owning type of the method is a canonical type and the member parent // supplied by RyuJIT is a concrete instantiation. if (type != method.OwningType) { if (type.IsArray) { method = ((ArrayType)type).GetArrayMethod(((ArrayMethod)method).Kind); } else { Debug.Assert(type.HasSameTypeDefinition(method.OwningType)); Instantiation methodInst = method.Instantiation; method = _compilation.TypeSystemContext.GetMethodForInstantiatedType(method.GetTypicalMethodDefinition(), (InstantiatedType)type); if (methodInst.Length > 0) { method = method.MakeInstantiatedMethod(methodInst); } } } } Get_CORINFO_SIG_INFO(method, sig: sig, scope: null); } private bool getMethodInfo(CORINFO_METHOD_STRUCT_* ftn, CORINFO_METHOD_INFO* info, CORINFO_CONTEXT_STRUCT* context) { MethodDesc method = HandleToObject(ftn); if (context != null && method.IsSharedByGenericInstantiations) { TypeSystemEntity ctx = entityFromContext(context); if (ctx is MethodDesc methodFromCtx && context != contextFromMethodBeingCompiled()) { Debug.Assert(method.GetTypicalMethodDefinition() == methodFromCtx.GetTypicalMethodDefinition()); method = methodFromCtx; } else if (ctx is InstantiatedType instantiatedCtxType) { MethodDesc instantiatedMethod = _compilation.TypeSystemContext.GetMethodForInstantiatedType(method.GetTypicalMethodDefinition(), instantiatedCtxType); if (method.HasInstantiation) { instantiatedMethod = _compilation.TypeSystemContext.GetInstantiatedMethod(instantiatedMethod, method.Instantiation); } method = instantiatedMethod; } } // Add an early CanInline check to see if referring to the IL of the target methods is // permitted from within this MethodBeingCompiled, the full CanInline check will be performed // later. if (!_compilation.CanInline(MethodBeingCompiled, method)) return false; MethodIL methodIL = method.IsUnboxingThunk() ? null : _compilation.GetMethodIL(method); return Get_CORINFO_METHOD_INFO(method, methodIL, info); } private bool haveSameMethodDefinition(CORINFO_METHOD_STRUCT_* methHnd1, CORINFO_METHOD_STRUCT_* methHnd2) { MethodDesc meth1 = HandleToObject(methHnd1); MethodDesc meth2 = HandleToObject(methHnd2); return meth1.GetTypicalMethodDefinition() == meth2.GetTypicalMethodDefinition(); } private CORINFO_CLASS_STRUCT_* getTypeDefinition(CORINFO_CLASS_STRUCT_* type) { return ObjectToHandle(HandleToObject(type).GetTypeDefinition()); } private CorInfoInline canInline(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* calleeHnd) { MethodDesc callerMethod = HandleToObject(callerHnd); MethodDesc calleeMethod = HandleToObject(calleeHnd); EcmaModule rootModule = (MethodBeingCompiled.OwningType as MetadataType)?.Module as EcmaModule; EcmaModule calleeModule = (calleeMethod.OwningType as MetadataType)?.Module as EcmaModule; // If this inline crosses module boundaries, ensure the modules agree on exception wrapping behavior. if ((rootModule != calleeModule) && (rootModule != null) && (calleeModule != null)) { if (rootModule.IsWrapNonExceptionThrows != calleeModule.IsWrapNonExceptionThrows) { var calleeIL = _compilation.GetMethodIL(calleeMethod); if (calleeIL.GetExceptionRegions().Length != 0) { // Fail inlining if root method and callee have different exception wrapping behavior return CorInfoInline.INLINE_FAIL; } } } if (_compilation.CanInline(callerMethod, calleeMethod)) { // No restrictions on inlining return CorInfoInline.INLINE_PASS; } else { // Call may not be inlined // // Note despite returning INLINE_NEVER here, in compilations where jitting is possible // the jit may still be able to inline this method. So we rely on reportInliningDecision // to not propagate this into metadata to short-circuit future inlining attempts. return CorInfoInline.INLINE_NEVER; } } #pragma warning disable CA1822 // Mark members as static private void reportTailCallDecision(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUCT_* calleeHnd, bool fIsTailPrefix, CorInfoTailCall tailCallResult, byte* reason) #pragma warning restore CA1822 // Mark members as static { } private void getEHinfo(CORINFO_METHOD_STRUCT_* ftn, uint EHnumber, ref CORINFO_EH_CLAUSE clause) { var methodIL = _compilation.GetMethodIL(HandleToObject(ftn)); var ehRegion = methodIL.GetExceptionRegions()[EHnumber]; clause.Flags = (CORINFO_EH_CLAUSE_FLAGS)ehRegion.Kind; clause.TryOffset = (uint)ehRegion.TryOffset; clause.TryLength = (uint)ehRegion.TryLength; clause.HandlerOffset = (uint)ehRegion.HandlerOffset; clause.HandlerLength = (uint)ehRegion.HandlerLength; clause.ClassTokenOrOffset = (uint)((ehRegion.Kind == ILExceptionRegionKind.Filter) ? ehRegion.FilterOffset : ehRegion.ClassToken); } private CORINFO_CLASS_STRUCT_* getMethodClass(CORINFO_METHOD_STRUCT_* method) { var m = HandleToObject(method); return ObjectToHandle(m.OwningType); } private bool resolveVirtualMethod(CORINFO_DEVIRTUALIZATION_INFO* info) { // Initialize OUT fields info->devirtualizedMethod = null; info->exactContext = null; info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN; info->isInstantiatingStub = false; info->needsMethodContext = false; TypeDesc objType = HandleToObject(info->objClass); // __Canon cannot be devirtualized if (objType.IsCanonicalDefinitionType(CanonicalFormKind.Any)) { info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_CANON; return false; } MethodDesc decl = HandleToObject(info->virtualMethod); // Transform from the unboxing thunk to the normal method decl = decl.IsUnboxingThunk() ? decl.GetUnboxedMethod() : decl; if ((info->context != null) && decl.OwningType.IsInterface) { TypeDesc ownerTypeDesc = typeFromContext(info->context); if (decl.OwningType != ownerTypeDesc) { Debug.Assert(ownerTypeDesc is InstantiatedType); decl = _compilation.TypeSystemContext.GetMethodForInstantiatedType(decl.GetTypicalMethodDefinition(), (InstantiatedType)ownerTypeDesc); } } MethodDesc originalImpl = _compilation.ResolveVirtualMethod(decl, objType, out info->detail); if (originalImpl == null) { // If this assert fires, we failed to devirtualize, probably due to a failure to resolve the // virtual to an exact target. This should never happen in practice if the input IL is valid, // and the algorithm for virtual function resolution is correct; however, if it does, this is // a safe condition, and we could delete this assert. This assert exists in order to help identify // cases where the virtual function resolution algorithm either does not function, or is not used // correctly. #if DEBUG if (info->detail == CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN) { Console.Error.WriteLine($"Failed devirtualization with unexpected unknown failure while compiling {MethodBeingCompiled} with decl {decl} targeting type {objType}"); Debug.Assert(info->detail != CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_UNKNOWN); } #endif return false; } TypeDesc owningType = originalImpl.OwningType; // RyuJIT expects to get the canonical form back MethodDesc impl = originalImpl.GetCanonMethodTarget(CanonicalFormKind.Specific); bool unboxingStub = impl.OwningType.IsValueType; MethodDesc nonUnboxingImpl = impl; if (unboxingStub) { impl = getUnboxingThunk(impl); } #if READYTORUN // As there are a variety of situations where the resolved virtual method may be different at compile and runtime (primarily due to subtle differences // in the virtual resolution algorithm between the runtime and the compiler, although details such as whether or not type equivalence is enabled // can also have an effect), record any decisions made, and if there are differences, simply skip use of the compiled method. var resolver = _compilation.NodeFactory.Resolver; MethodWithToken methodWithTokenDecl; if (info->pResolvedTokenVirtualMethod != null) { methodWithTokenDecl = ComputeMethodWithToken(decl, ref *info->pResolvedTokenVirtualMethod, null, false); } else { ModuleToken declToken = resolver.GetModuleTokenForMethod(decl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: false); if (declToken.IsNull) { info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE; return false; } if (!_compilation.CompilationModuleGroup.VersionsWithTypeReference(decl.OwningType)) { info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_FAILED_DECL_NOT_REPRESENTABLE; return false; } methodWithTokenDecl = new MethodWithToken(decl, declToken, null, false, null, devirtualizedMethodOwner: decl.OwningType); } MethodWithToken methodWithTokenImpl; #endif if (decl == originalImpl) { #if READYTORUN methodWithTokenImpl = methodWithTokenDecl; #endif if (info->pResolvedTokenVirtualMethod != null) { info->resolvedTokenDevirtualizedMethod = *info->pResolvedTokenVirtualMethod; } else { info->resolvedTokenDevirtualizedMethod = CreateResolvedTokenFromMethod(this, decl #if READYTORUN , methodWithTokenDecl #endif ); } } else { #if READYTORUN methodWithTokenImpl = new MethodWithToken(nonUnboxingImpl, resolver.GetModuleTokenForMethod(nonUnboxingImpl.GetTypicalMethodDefinition(), allowDynamicallyCreatedReference: false, throwIfNotFound: true), null, unboxingStub, null, devirtualizedMethodOwner: impl.OwningType); #endif info->resolvedTokenDevirtualizedMethod = CreateResolvedTokenFromMethod(this, impl #if READYTORUN , methodWithTokenImpl #endif ); } if (unboxingStub) { info->resolvedTokenDevirtualizedUnboxedMethod = info->resolvedTokenDevirtualizedMethod; info->resolvedTokenDevirtualizedUnboxedMethod.tokenContext = contextFromMethod(nonUnboxingImpl); info->resolvedTokenDevirtualizedUnboxedMethod.hMethod = ObjectToHandle(nonUnboxingImpl); } else { info->resolvedTokenDevirtualizedUnboxedMethod = default(CORINFO_RESOLVED_TOKEN); } #if READYTORUN // Testing has not shown that concerns about virtual matching are significant // Only generate verification for builds with the stress mode enabled if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout) { if (!methodWithTokenDecl.Method.OwningType.IsValueType || !methodWithTokenImpl.Method.OwningType.IsValueType) { ISymbolNode virtualResolutionNode = _compilation.SymbolNodeFactory.CheckVirtualFunctionOverride(methodWithTokenDecl, objType, methodWithTokenImpl); AddPrecodeFixup(virtualResolutionNode); } } #endif info->detail = CORINFO_DEVIRTUALIZATION_DETAIL.CORINFO_DEVIRTUALIZATION_SUCCESS; info->devirtualizedMethod = ObjectToHandle(impl); info->isInstantiatingStub = false; info->exactContext = contextFromType(owningType); return true; static CORINFO_RESOLVED_TOKEN CreateResolvedTokenFromMethod(CorInfoImpl jitInterface, MethodDesc method #if READYTORUN , MethodWithToken methodWithToken #endif ) { #if !READYTORUN MethodDesc unboxedMethodDesc = method.IsUnboxingThunk() ? method.GetUnboxedMethod() : method; var methodWithToken = new { Method = unboxedMethodDesc, OwningType = unboxedMethodDesc.OwningType, }; #endif CORINFO_RESOLVED_TOKEN result = default(CORINFO_RESOLVED_TOKEN); MethodILScope scope = jitInterface._compilation.GetMethodIL(methodWithToken.Method); scope ??= EcmaMethodILScope.Create((EcmaMethod)methodWithToken.Method.GetTypicalMethodDefinition()); result.tokenScope = jitInterface.ObjectToHandle(scope); result.tokenContext = jitInterface.contextFromMethod(method); #if READYTORUN result.token = methodWithToken.Token.Token; if (methodWithToken.Token.TokenType != CorTokenType.mdtMethodDef) { Debug.Assert(false); // This should never happen, but we protect against total failure with the throw below. throw new RequiresRuntimeJitException("Attempt to devirtualize and unable to create token for devirtualized method"); } #else result.token = (mdToken)0x06BAAAAD; #endif result.tokenType = CorInfoTokenKind.CORINFO_TOKENKIND_DevirtualizedMethod; result.hClass = jitInterface.ObjectToHandle(methodWithToken.OwningType); result.hMethod = jitInterface.ObjectToHandle(method); return result; } } private CORINFO_METHOD_STRUCT_* getUnboxedEntry(CORINFO_METHOD_STRUCT_* ftn, ref bool requiresInstMethodTableArg) { MethodDesc result = null; requiresInstMethodTableArg = false; MethodDesc method = HandleToObject(ftn); if (method.IsUnboxingThunk()) { result = method.GetUnboxedMethod(); requiresInstMethodTableArg = method.RequiresInstMethodTableArg(); } return result != null ? ObjectToHandle(result) : null; } private CORINFO_METHOD_STRUCT_* getInstantiatedEntry(CORINFO_METHOD_STRUCT_* ftn, CORINFO_METHOD_STRUCT_** methodArg, CORINFO_CLASS_STRUCT_** classArg) { *methodArg = null; *classArg = null; return null; } private CORINFO_METHOD_STRUCT_* getAsyncOtherVariant(CORINFO_METHOD_STRUCT_* ftn, ref bool variantIsThunk) { MethodDesc method = HandleToObject(ftn); if (method.IsAsyncVariant()) { method = method.GetTargetOfAsyncVariant(); } else if (method.Signature.ReturnsTaskOrValueTask()) { method = method.GetAsyncVariant(); } else { variantIsThunk = false; return null; } variantIsThunk = method?.IsAsyncThunk() ?? false; return ObjectToHandle(method); } private CORINFO_CLASS_STRUCT_* getDefaultComparerClass(CORINFO_CLASS_STRUCT_* elemType) { TypeDesc comparand = HandleToObject(elemType); TypeDesc comparer = IL.Stubs.ComparerIntrinsics.GetComparerForType(comparand); return comparer != null ? ObjectToHandle(comparer) : null; } private CORINFO_CLASS_STRUCT_* getDefaultEqualityComparerClass(CORINFO_CLASS_STRUCT_* elemType) { TypeDesc comparand = HandleToObject(elemType); TypeDesc comparer = IL.Stubs.ComparerIntrinsics.GetEqualityComparerForType(comparand); return comparer != null ? ObjectToHandle(comparer) : null; } private CORINFO_CLASS_STRUCT_* getSZArrayHelperEnumeratorClass(CORINFO_CLASS_STRUCT_* elemType) { TypeDesc elementType = HandleToObject(elemType); MetadataType placeholderType = _compilation.TypeSystemContext.SystemModule.GetType("System"u8, "SZGenericArrayEnumerator`1"u8, throwIfNotFound: false); if (placeholderType == null) { return null; } return ObjectToHandle(placeholderType.MakeInstantiatedType(elementType)); } private bool isIntrinsicType(CORINFO_CLASS_STRUCT_* classHnd) { TypeDesc type = HandleToObject(classHnd); return type.IsIntrinsic; } private CorInfoCallConvExtension getUnmanagedCallConv(CORINFO_METHOD_STRUCT_* method, CORINFO_SIG_INFO* sig, ref bool pSuppressGCTransition) { pSuppressGCTransition = false; if (method != null) { MethodDesc methodDesc = HandleToObject(method); CorInfoCallConvExtension callConv = GetUnmanagedCallConv(HandleToObject(method), out pSuppressGCTransition); return callConv; } else { Debug.Assert(sig != null); CorInfoCallConvExtension callConv = GetUnmanagedCallConv(HandleToObject(sig->methodSignature), out pSuppressGCTransition); return callConv; } } private static CorInfoCallConvExtension GetUnmanagedCallConv(MethodDesc methodDesc, out bool suppressGCTransition) { UnmanagedCallingConventions callingConventions; if ((methodDesc.Signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) == 0) { if (methodDesc.IsPInvoke) { callingConventions = methodDesc.GetPInvokeMethodCallingConventions(); } else { Debug.Assert(methodDesc.IsUnmanagedCallersOnly); callingConventions = methodDesc.GetUnmanagedCallersOnlyMethodCallingConventions(); } } else { callingConventions = methodDesc.Signature.GetStandaloneMethodSignatureCallingConventions(); } return ToCorInfoCallConvExtension(callingConventions, out suppressGCTransition); } private static CorInfoCallConvExtension GetUnmanagedCallConv(MethodSignature signature, out bool suppressGCTransition) { return ToCorInfoCallConvExtension(signature.GetStandaloneMethodSignatureCallingConventions(), out suppressGCTransition); } private static CorInfoCallConvExtension ToCorInfoCallConvExtension(UnmanagedCallingConventions callConvs, out bool suppressGCTransition) { CorInfoCallConvExtension result; switch (callConvs & UnmanagedCallingConventions.CallingConventionMask) { case UnmanagedCallingConventions.Cdecl: result = CorInfoCallConvExtension.C; break; case UnmanagedCallingConventions.Stdcall: result = CorInfoCallConvExtension.Stdcall; break; case UnmanagedCallingConventions.Thiscall: result = CorInfoCallConvExtension.Thiscall; break; case UnmanagedCallingConventions.Fastcall: result = CorInfoCallConvExtension.Fastcall; break; case UnmanagedCallingConventions.Swift: result = CorInfoCallConvExtension.Swift; break; default: ThrowHelper.ThrowInvalidProgramException(); result = CorInfoCallConvExtension.Managed; // unreachable break; } if ((callConvs & UnmanagedCallingConventions.IsMemberFunction) != 0) { result = result switch { CorInfoCallConvExtension.C => CorInfoCallConvExtension.CMemberFunction, CorInfoCallConvExtension.Stdcall => CorInfoCallConvExtension.StdcallMemberFunction, CorInfoCallConvExtension.Fastcall => CorInfoCallConvExtension.FastcallMemberFunction, _ => result, }; } suppressGCTransition = (callConvs & UnmanagedCallingConventions.IsSuppressGcTransition) != 0; return result; } private bool satisfiesMethodConstraints(CORINFO_CLASS_STRUCT_* parent, CORINFO_METHOD_STRUCT_* method) { throw new NotImplementedException("satisfiesMethodConstraints"); } private void setPatchpointInfo(PatchpointInfo* patchpointInfo) { throw new NotImplementedException("setPatchpointInfo"); } private PatchpointInfo* getOSRInfo(ref uint ilOffset) { throw new NotImplementedException("getOSRInfo"); } #pragma warning disable CA1822 // Mark members as static private void methodMustBeLoadedBeforeCodeIsRun(CORINFO_METHOD_STRUCT_* method) #pragma warning restore CA1822 // Mark members as static { } private static object ResolveTokenWithSubstitution(MethodILScope methodIL, mdToken token, Instantiation typeInst, Instantiation methodInst) { // Grab the generic definition of the method IL, resolve the token within the definition, // and instantiate it with the given context. object result = methodIL.GetMethodILScopeDefinition().GetObject((int)token); if (result is MethodDesc methodResult) { result = methodResult.InstantiateSignature(typeInst, methodInst); } else if (result is FieldDesc fieldResult) { result = fieldResult.InstantiateSignature(typeInst, methodInst); } else { result = ((TypeDesc)result).InstantiateSignature(typeInst, methodInst); } return result; } private static object ResolveTokenInScope(MethodILScope methodIL, object typeOrMethodContext, mdToken token) { MethodDesc owningMethod = methodIL.OwningMethod; // If token context differs from the scope, it means we're inlining. // If we're inlining a shared method body, we might be able to un-share // the referenced token and avoid runtime lookups. // Resolve the token in the inlining context. object result; if (owningMethod != typeOrMethodContext && owningMethod.IsCanonicalMethod(CanonicalFormKind.Any)) { Instantiation methodInst = default; Instantiation typeInst; if (typeOrMethodContext is TypeDesc typeContext) { Debug.Assert(typeContext.HasSameTypeDefinition(owningMethod.OwningType) || typeContext.IsArray); typeInst = typeContext.Instantiation; } else { var methodContext = (MethodDesc)typeOrMethodContext; // Allow cases where the method's do not have instantiations themselves, if // 1. The method defining the context is generic, but the target method is not // 2. Both methods are not generic // 3. The methods are the same generic // AND // The methods are on the same type Debug.Assert((methodContext.HasInstantiation && !owningMethod.HasInstantiation) || (!methodContext.HasInstantiation && !owningMethod.HasInstantiation) || methodContext.GetTypicalMethodDefinition() == owningMethod.GetTypicalMethodDefinition() || (owningMethod.Name.SequenceEqual("CreateDefaultInstance"u8) && methodContext.Name.SequenceEqual("CreateInstance"u8))); Debug.Assert(methodContext.OwningType.HasSameTypeDefinition(owningMethod.OwningType)); typeInst = methodContext.OwningType.Instantiation; methodInst = methodContext.Instantiation; } result = ResolveTokenWithSubstitution(methodIL, token, typeInst, methodInst); } else { // Not inlining - just resolve the token within the methodIL result = methodIL.GetObject((int)token); } return result; } private object GetRuntimeDeterminedObjectForToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) { // Since RyuJIT operates on canonical types (as opposed to runtime determined ones), but the // dependency analysis operates on runtime determined ones, we convert the resolved token // to the runtime determined form (e.g. Foo<__Canon> becomes Foo<T__Canon>). var methodIL = HandleToObject(pResolvedToken.tokenScope); var typeOrMethodContext = (pResolvedToken.tokenContext == contextFromMethodBeingCompiled()) ? MethodBeingCompiled : HandleToObject((void*)pResolvedToken.tokenContext); object result = GetRuntimeDeterminedObjectForToken(methodIL, typeOrMethodContext, pResolvedToken.token); if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr) result = ((TypeDesc)result).MakeArrayType(); if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await) result = _compilation.TypeSystemContext.GetAsyncVariantMethod((MethodDesc)result); return result; } private static object GetRuntimeDeterminedObjectForToken(MethodILScope methodIL, object typeOrMethodContext, mdToken token) { object result = ResolveTokenInScope(methodIL, typeOrMethodContext, token); if (result is MethodDesc method) { if (method.IsSharedByGenericInstantiations) { MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); result = ResolveTokenWithSubstitution(methodIL, token, sharedMethod.OwningType.Instantiation, sharedMethod.Instantiation); Debug.Assert(((MethodDesc)result).IsRuntimeDeterminedExactMethod); } } else if (result is FieldDesc field) { if (field.OwningType.IsCanonicalSubtype(CanonicalFormKind.Any)) { MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); result = ResolveTokenWithSubstitution(methodIL, token, sharedMethod.OwningType.Instantiation, sharedMethod.Instantiation); Debug.Assert(((FieldDesc)result).OwningType.IsRuntimeDeterminedSubtype); } } else { TypeDesc type = (TypeDesc)result; if (type.IsCanonicalSubtype(CanonicalFormKind.Any)) { MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); result = ResolveTokenWithSubstitution(methodIL, token, sharedMethod.OwningType.Instantiation, sharedMethod.Instantiation); Debug.Assert(((TypeDesc)result).IsRuntimeDeterminedSubtype || /* If the resolved type is not runtime determined there's a chance we went down this path because there was a literal typeof(__Canon) in the compiled IL - check for that by resolving the token in the definition. */ ((TypeDesc)methodIL.GetMethodILScopeDefinition().GetObject((int)token)).IsCanonicalDefinitionType(CanonicalFormKind.Any)); } } return result; } private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) { var methodIL = HandleToObject(pResolvedToken.tokenScope); var typeOrMethodContext = (pResolvedToken.tokenContext == contextFromMethodBeingCompiled()) ? MethodBeingCompiled : HandleToObject((void*)pResolvedToken.tokenContext); object result = ResolveTokenInScope(methodIL, typeOrMethodContext, pResolvedToken.token); pResolvedToken.hClass = null; pResolvedToken.hMethod = null; pResolvedToken.hField = null; #if READYTORUN TypeDesc owningType = methodIL.OwningMethod.GetTypicalMethodDefinition().OwningType; bool recordToken; if (!_compilation.CompilationModuleGroup.VersionsWithMethodBody(methodIL.OwningMethod.GetTypicalMethodDefinition())) { recordToken = (methodIL.GetMethodILScopeDefinition() is IMethodTokensAreUseableInCompilation) && owningType is EcmaType; } else { recordToken = (_compilation.CompilationModuleGroup.VersionsWithType(owningType) || _compilation.CompilationModuleGroup.CrossModuleInlineableType(owningType)) && owningType is EcmaType; recordToken &= methodIL.GetMethodILScopeDefinition() is IMethodTokensAreUseableInCompilation || methodIL.GetMethodILScopeDefinition() is IEcmaMethodIL; } #endif if (result is MethodDesc method) { #if !SUPPORT_JIT _compilation.TypeSystemContext.EnsureLoadableMethod(method); #endif #if READYTORUN if (recordToken) { ModuleToken methodModuleToken = HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation); Debug.Assert(!strippedInstantiation); var resolver = _compilation.NodeFactory.Resolver; resolver.AddModuleTokenForMethod(method, methodModuleToken); ValidateSafetyOfUsingTypeEquivalenceInSignature(method.Signature); } #else _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, method); #endif if (pResolvedToken.tokenType is CorInfoTokenKind.CORINFO_TOKENKIND_Await) { // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). // we cannot resolve to an Async variant in such case. // return NULL, so that caller would re-resolve as a regular method call bool allowAsyncVariant = method.GetTypicalMethodDefinition().Signature.ReturnsTaskOrValueTask(); // Don't get async variant of Delegate.Invoke method; the pointed to method is not an async variant either. allowAsyncVariant = allowAsyncVariant && !method.OwningType.IsDelegate; method = allowAsyncVariant ? _compilation.TypeSystemContext.GetAsyncVariantMethod(method) : null; } if (method != null) { pResolvedToken.hMethod = ObjectToHandle(method); pResolvedToken.hClass = ObjectToHandle(method.OwningType); } else { pResolvedToken.hMethod = null; pResolvedToken.hClass = null; } } else if (result is FieldDesc) { FieldDesc field = result as FieldDesc; // References to literal fields from IL body should never resolve. // The CLR would throw a MissingFieldException while jitting and so should we. if (field.IsLiteral) ThrowHelper.ThrowMissingFieldException(field.OwningType, field.GetName()); pResolvedToken.hField = ObjectToHandle(field); TypeDesc owningClass = field.OwningType; pResolvedToken.hClass = ObjectToHandle(owningClass); #if !SUPPORT_JIT _compilation.TypeSystemContext.EnsureLoadableType(owningClass); #endif #if !READYTORUN _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, field); #else ValidateSafetyOfUsingTypeEquivalenceOfType(field.FieldType); #endif } else { TypeDesc type = (TypeDesc)result; #if READYTORUN if (recordToken) { _compilation.NodeFactory.Resolver.AddModuleTokenForType(type, HandleToModuleToken(ref pResolvedToken, out bool strippedInstantiation)); Debug.Assert(!strippedInstantiation); } #endif if (pResolvedToken.tokenType == CorInfoTokenKind.CORINFO_TOKENKIND_Newarr) { if (type.IsVoid) ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, methodIL.OwningMethod); type = type.MakeArrayType(); } pResolvedToken.hClass = ObjectToHandle(type); #if !SUPPORT_JIT _compilation.TypeSystemContext.EnsureLoadableType(type); #endif } pResolvedToken.pTypeSpec = null; pResolvedToken.cbTypeSpec = 0; pResolvedToken.pMethodSpec = null; pResolvedToken.cbMethodSpec = 0; } private void findSig(CORINFO_MODULE_STRUCT_* module, uint sigTOK, CORINFO_CONTEXT_STRUCT* context, CORINFO_SIG_INFO* sig) { var methodIL = HandleToObject(module); var methodSig = (MethodSignature)methodIL.GetObject((int)sigTOK); Get_CORINFO_SIG_INFO(methodSig, sig, methodIL); #if !READYTORUN // Check whether we need to report this as a fat pointer call if (_compilation.IsFatPointerCandidate(methodIL.OwningMethod, methodSig)) { sig->flags |= CorInfoSigInfoFlags.CORINFO_SIGFLAG_FAT_CALL; } #else VerifyMethodSignatureIsStable(methodSig); #endif } private void findCallSiteSig(CORINFO_MODULE_STRUCT_* module, uint methTOK, CORINFO_CONTEXT_STRUCT* context, CORINFO_SIG_INFO* sig) { var methodIL = HandleToObject(module); Get_CORINFO_SIG_INFO(((MethodDesc)methodIL.GetObject((int)methTOK)), sig: sig, methodIL); } private CORINFO_CLASS_STRUCT_* getTokenTypeAsHandle(ref CORINFO_RESOLVED_TOKEN pResolvedToken) { WellKnownType result = WellKnownType.RuntimeTypeHandle; if (pResolvedToken.hMethod != null) { result = WellKnownType.RuntimeMethodHandle; } else if (pResolvedToken.hField != null) { result = WellKnownType.RuntimeFieldHandle; } return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(result)); } private static CorInfoCanSkipVerificationResult canSkipVerification(CORINFO_MODULE_STRUCT_* module) { return CorInfoCanSkipVerificationResult.CORINFO_VERIFICATION_CAN_SKIP; } private int getStringLiteral(CORINFO_MODULE_STRUCT_* module, uint metaTOK, char* buffer, int size, int startIndex) { Debug.Assert(size >= 0); Debug.Assert(startIndex >= 0); MethodILScope methodIL = HandleToObject(module); string str = (string)methodIL.GetObject((int)metaTOK); int result = (str.Length >= startIndex) ? (str.Length - startIndex) : 0; if (buffer != null && result != 0) { // Copy str's content to buffer str.AsSpan(startIndex, Math.Min(size, result)).CopyTo(new Span<char>(buffer, size)); } return result; } private nuint printObjectDescription(CORINFO_OBJECT_STRUCT_* handle, byte* buffer, nuint bufferSize, nuint* pRequiredBufferSize) { Debug.Assert(handle != null); return PrintFromUtf16(HandleToObject(handle).ToString(), buffer, bufferSize, pRequiredBufferSize); } internal static nuint PrintFromUtf16(ReadOnlySpan<char> utf16, byte* buffer, nuint bufferSize, nuint* pRequiredBufferSize) { int written = 0; if (bufferSize > 0) { OperationStatus status = Utf8.FromUtf16(utf16, new Span<byte>(buffer, checked((int)(bufferSize - 1))), out _, out written); // Always null-terminate buffer[written] = 0; if (status == OperationStatus.Done) { if (pRequiredBufferSize != null) { *pRequiredBufferSize = (nuint)written + 1; } return (nuint)written; } } if (pRequiredBufferSize != null) { *pRequiredBufferSize = (nuint)Encoding.UTF8.GetByteCount(utf16) + 1; } return (nuint)written; } private CorInfoType asCorInfoType(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); return asCorInfoType(type); } private byte* getClassNameFromMetadata(CORINFO_CLASS_STRUCT_* cls, byte** namespaceName) { TypeDesc type = HandleToObject(cls); if (type.GetTypeDefinition() is EcmaType ecmaType) { var reader = ecmaType.MetadataReader; if (namespaceName != null) *namespaceName = reader.GetTypeNamespacePointer(ecmaType.Handle); return reader.GetTypeNamePointer(ecmaType.Handle); } else if (type is MetadataType mdType) { if (namespaceName != null) *namespaceName = (byte*)GetPin(SpanToPinnableBytes(mdType.Namespace)); return (byte*)GetPin(SpanToPinnableBytes(mdType.Name)); } if (namespaceName != null) *namespaceName = null; return null; } private CORINFO_CLASS_STRUCT_* getTypeInstantiationArgument(CORINFO_CLASS_STRUCT_* cls, uint index) { TypeDesc type = HandleToObject(cls); Instantiation inst = type.Instantiation; return index < (uint)inst.Length ? ObjectToHandle(inst[(int)index]) : null; } #pragma warning disable CA1822 // Mark members as static private CORINFO_CLASS_STRUCT_* getMethodInstantiationArgument(CORINFO_METHOD_STRUCT_* ftn, uint index) { #pragma warning restore CA1822 // Mark members as static return null; } private nuint printClassName(CORINFO_CLASS_STRUCT_* cls, byte* buffer, nuint bufferSize, nuint* pRequiredBufferSize) { TypeDesc type = HandleToObject(cls); string name = JitTypeNameFormatter.Instance.FormatName(type); return PrintFromUtf16(name, buffer, bufferSize, pRequiredBufferSize); } private bool isValueClass(CORINFO_CLASS_STRUCT_* cls) { return HandleToObject(cls).IsValueType; } private uint getClassAttribs(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); return getClassAttribsInternal(type); } private uint getClassAttribsInternal(TypeDesc type) { // TODO: Support for verification (CORINFO_FLG_GENERIC_TYPE_VARIABLE) CorInfoFlag result = (CorInfoFlag)0; var metadataType = type as MetadataType; // The array flag is used to identify the faked-up methods on // array types, i.e. .ctor, Get, Set and Address if (type.IsArray) result |= CorInfoFlag.CORINFO_FLG_ARRAY; if (type.IsInterface) result |= CorInfoFlag.CORINFO_FLG_INTERFACE; if (type.IsArray || type.IsString) result |= CorInfoFlag.CORINFO_FLG_VAROBJSIZE; if (type.IsValueType) { result |= CorInfoFlag.CORINFO_FLG_VALUECLASS; if (metadataType.IsByRefLike) result |= CorInfoFlag.CORINFO_FLG_BYREF_LIKE; if (metadataType.IsUnsafeValueType) result |= CorInfoFlag.CORINFO_FLG_UNSAFE_VALUECLASS; if (metadataType.IsInlineArray) result |= CorInfoFlag.CORINFO_FLG_INDEXABLE_FIELDS; if (metadataType.IsExtendedLayout) { if (metadataType.GetClassLayout().Kind == MetadataLayoutKind.CUnion) { result |= CorInfoFlag.CORINFO_FLG_OVERLAPPING_FIELDS; } } } if (type.IsCanonicalSubtype(CanonicalFormKind.Any)) result |= CorInfoFlag.CORINFO_FLG_SHAREDINST; if (type.IsDelegate) result |= CorInfoFlag.CORINFO_FLG_DELEGATE; if (_compilation.IsEffectivelySealed(type)) result |= CorInfoFlag.CORINFO_FLG_FINAL; if (type.IsIntrinsic) result |= CorInfoFlag.CORINFO_FLG_INTRINSIC_TYPE; if (metadataType != null) { if (metadataType.ContainsGCPointers) result |= CorInfoFlag.CORINFO_FLG_CONTAINS_GC_PTR; if (metadataType.IsBeforeFieldInit) { bool makeBeforeFieldInit = true; #if READYTORUN makeBeforeFieldInit &= _compilation.CompilationModuleGroup.VersionsWithType(type); #endif if (makeBeforeFieldInit) { result |= CorInfoFlag.CORINFO_FLG_BEFOREFIELDINIT; } } // Assume overlapping fields for explicit layout. if (metadataType.IsExplicitLayout) result |= CorInfoFlag.CORINFO_FLG_OVERLAPPING_FIELDS; if (metadataType.IsAbstract) result |= CorInfoFlag.CORINFO_FLG_ABSTRACT; } return (uint)result; } private byte* getClassAssemblyName(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); if (type is MetadataType mdType) { return (byte*)GetPin(StringToUTF8(mdType.Module.Assembly.GetName().Name)); } return null; } #pragma warning disable CA1822 // Mark members as static private void* LongLifetimeMalloc(nuint sz) #pragma warning restore CA1822 // Mark members as static { return NativeMemory.Alloc(sz); } #pragma warning disable CA1822 // Mark members as static private void LongLifetimeFree(void* obj) #pragma warning restore CA1822 // Mark members as static { NativeMemory.Free(obj); } private void* getClassStaticDynamicInfo(CORINFO_CLASS_STRUCT_* cls) { throw new NotImplementedException("getClassStaticDynamicInfo"); } private void* getClassThreadStaticDynamicInfo(CORINFO_CLASS_STRUCT_* cls) { throw new NotImplementedException("getClassThreadStaticDynamicInfo"); } private uint getClassSize(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); LayoutInt classSize = type.GetElementSize(); #if READYTORUN if (classSize.IsIndeterminate) { throw new RequiresRuntimeJitException(type); } if (NeedsTypeLayoutCheck(type)) { ISymbolNode node = _compilation.SymbolNodeFactory.CheckTypeLayout(type); AddPrecodeFixup(node); } #endif return (uint)classSize.AsInt; } private uint getHeapClassSize(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); Debug.Assert(!type.IsValueType); Debug.Assert(type.IsDefType); Debug.Assert(!type.IsString); #if READYTORUN Debug.Assert(_compilation.IsInheritanceChainLayoutFixedInCurrentVersionBubble(type)); #endif return (uint)((DefType)type).InstanceByteCount.AsInt; } private bool canAllocateOnStack(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); Debug.Assert(!type.IsValueType); Debug.Assert(type.IsDefType); bool result = !type.HasFinalizer; #if READYTORUN if (!_compilation.IsInheritanceChainLayoutFixedInCurrentVersionBubble(type)) result = false; #endif return result; } /// <summary> /// Managed implementation of CEEInfo::getClassAlignmentRequirementStatic /// </summary> public static int GetClassAlignmentRequirementStatic(DefType type) { int alignment = type.Context.Target.PointerSize; if (type is MetadataType metadataType && !metadataType.IsAutoLayout) { if (metadataType.IsSequentialLayout || MarshalUtils.IsBlittableType(metadataType)) { alignment = metadataType.InstanceFieldAlignment.AsInt; } } if (type.Context.Target.Architecture == TargetArchitecture.ARM && alignment < 8 && type.RequiresAlign8()) { // If the structure contains 64-bit primitive fields and the platform requires 8-byte alignment for // such fields then make sure we return at least 8-byte alignment. Note that it's technically possible // to create unmanaged APIs that take unaligned structures containing such fields and this // unconditional alignment bump would cause us to get the calling convention wrong on platforms such // as ARM. If we see such cases in the future we'd need to add another control (such as an alignment // property for the StructLayout attribute or a marshaling directive attribute for p/invoke arguments) // that allows more precise control. For now we'll go with the likely scenario. alignment = 8; } return alignment; } private Dictionary<DefType, bool> _doubleAlignHeuristicCache = new Dictionary<DefType, bool>(); //******************************************************************************* // // Heuristic to determine if we should have instances of this class 8 byte aligned // private static bool ShouldAlign8(int dwR8Fields, int dwTotalFields) { return dwR8Fields*2>dwTotalFields && dwR8Fields>=2; } private static bool ShouldAlign8(DefType type) { int instanceFields = 0; int doubleFields = 0; var doubleType = type.Context.GetWellKnownType(WellKnownType.Double); foreach (var field in type.GetFields()) { if (field.IsStatic) continue; instanceFields++; if (field.FieldType == doubleType) doubleFields++; } return ShouldAlign8(doubleFields, instanceFields); } private uint getClassAlignmentRequirement(CORINFO_CLASS_STRUCT_* cls, bool fDoubleAlignHint) { DefType type = (DefType)HandleToObject(cls); var target = type.Context.Target; if (fDoubleAlignHint) { if (target.Architecture == TargetArchitecture.X86) { if ((type.IsValueType) && (type.InstanceFieldAlignment.AsInt > 4)) { // On X86, double aligning the stack is expensive. if fDoubleAlignHint is true // only align the local variable if it has a large enough fraction of double fields // in comparison to the total field count. if (!_doubleAlignHeuristicCache.TryGetValue(type, out bool doDoubleAlign)) { doDoubleAlign = ShouldAlign8(type); _doubleAlignHeuristicCache.Add(type, doDoubleAlign); } // Return the size of the double align hint. Ignore the actual alignment info account // so that structs with 64-bit integer fields do not trigger double aligned frames on x86. if (doDoubleAlign) return 8; } return (uint)target.PointerSize; } } return (uint)GetClassAlignmentRequirementStatic(type); } private int MarkGcField(byte* gcPtrs, CorInfoGCType gcType) { // Ensure that if we have multiple fields with the same offset, // that we don't double count the data in the gc layout. if (*gcPtrs == (byte)CorInfoGCType.TYPE_GC_NONE) { *gcPtrs = (byte)gcType; return 1; } else { Debug.Assert(*gcPtrs == (byte)gcType); return 0; } } private int GatherClassGCLayout(MetadataType type, byte* gcPtrs) { int result = 0; if (type.BaseType is { ContainsGCPointers: true } baseType) result += GatherClassGCLayout(baseType, gcPtrs); bool isInlineArray = type.IsInlineArray; foreach (var field in type.GetFields()) { if (field.IsStatic) continue; CorInfoGCType gcType = CorInfoGCType.TYPE_GC_NONE; var fieldType = field.FieldType; if (fieldType.IsValueType) { var fieldDefType = (DefType)fieldType; if (!fieldDefType.ContainsGCPointers && !fieldDefType.IsByRefLike) continue; gcType = CorInfoGCType.TYPE_GC_OTHER; } else if (fieldType.IsGCPointer) { gcType = CorInfoGCType.TYPE_GC_REF; } else if (fieldType.IsByRef) { gcType = CorInfoGCType.TYPE_GC_BYREF; } else { continue; } Debug.Assert(field.Offset.AsInt % PointerSize == 0); byte* fieldGcPtrs = gcPtrs + field.Offset.AsInt / PointerSize; if (gcType == CorInfoGCType.TYPE_GC_OTHER) { result += GatherClassGCLayout((MetadataType)fieldType, fieldGcPtrs); } else { result += MarkGcField(fieldGcPtrs, gcType); } if (isInlineArray) { if (result > 0) { Debug.Assert(field.Offset.AsInt == 0); int totalLayoutSize = type.GetElementSize().AsInt / PointerSize; int elementLayoutSize = fieldType.GetElementSize().AsInt / PointerSize; int gcPointersInElement = result; for (int offset = elementLayoutSize; offset < totalLayoutSize; offset += elementLayoutSize) { Buffer.MemoryCopy(gcPtrs, gcPtrs + offset, elementLayoutSize, elementLayoutSize); result += gcPointersInElement; } } // inline array has only one element field break; } } return result; } private uint getClassGClayout(CORINFO_CLASS_STRUCT_* cls, byte* gcPtrs) { uint result = 0; MetadataType type = (MetadataType)HandleToObject(cls); uint size = type.IsValueType ? getClassSize(cls) : getHeapClassSize(cls); new Span<byte>(gcPtrs, (int)((size + PointerSize - 1) / PointerSize)).Clear(); if (type.ContainsGCPointers || type.IsByRefLike) { result = (uint)GatherClassGCLayout(type, gcPtrs); } return result; } private Dictionary<TypeDesc, uint> _classNumInstanceFields = new(); private uint getClassNumInstanceFields(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); var lookupType = type.GetTypeDefinition(); // The number of fields on an instantiation is the same as on the generic definition if (_classNumInstanceFields.TryGetValue(lookupType, out uint numInstanceFields)) return numInstanceFields; numInstanceFields = 0; foreach (var field in type.GetFields()) { if (!field.IsStatic) numInstanceFields++; } _classNumInstanceFields.Add(lookupType, numInstanceFields); return numInstanceFields; } private CORINFO_FIELD_STRUCT_* getFieldInClass(CORINFO_CLASS_STRUCT_* clsHnd, int num) { TypeDesc classWithFields = HandleToObject(clsHnd); int iCurrentFoundField = -1; foreach (var field in classWithFields.GetFields()) { if (field.IsStatic) continue; ++iCurrentFoundField; if (iCurrentFoundField == num) { return ObjectToHandle(field); } } // We could not find the field that was searched for. throw new InvalidOperationException(); } private GetTypeLayoutResult GetTypeLayoutHelper(MetadataType type, uint parentIndex, uint baseOffs, FieldDesc field, CORINFO_TYPE_LAYOUT_NODE* treeNodes, nuint maxTreeNodes, nuint* numTreeNodes) { if (*numTreeNodes >= maxTreeNodes) { return GetTypeLayoutResult.Partial; } uint structNodeIndex = (uint)(*numTreeNodes)++; CORINFO_TYPE_LAYOUT_NODE* parNode = &treeNodes[structNodeIndex]; parNode->simdTypeHnd = null; parNode->diagFieldHnd = field == null ? null : ObjectToHandle(field); parNode->parent = parentIndex; parNode->offset = baseOffs; parNode->size = (uint)type.GetElementSize().AsInt; parNode->numFields = 0; parNode->type = CorInfoType.CORINFO_TYPE_VALUECLASS; parNode->hasSignificantPadding = type.IsExplicitLayout || (type.IsSequentialLayout && type.GetClassLayout().Size != 0); #if READYTORUN // The contract of getTypeLayout is carefully crafted to still // allow us to return hints about fields even for types outside the // version bubble. The general idea is that the JIT does not use // the information returned by this function to create new // optimizations out of the blue, but only as a hint to optimize // existing field uses more thoroughly. In particular the uses of // fields outside the version bubble are only non-opaque and // amenable to the optimizations that this unlocks if they already // went through EncodeFieldBaseOffset. // if (!parNode->hasSignificantPadding && !_compilation.IsLayoutFixedInCurrentVersionBubble(type)) { // For types without fixed layout the JIT is not allowed to // rely on padding bits being insignificant, since fields could // be added later inside that padding without invalidating the // generated code. parNode->hasSignificantPadding = true; } #endif // The intrinsic SIMD/HW SIMD types have a lot of fields that the JIT does // not care about since they are considered primitives by the JIT. if (type.IsIntrinsic) { ReadOnlySpan<byte> ns = type.Namespace; if (ns.SequenceEqual("System.Runtime.Intrinsics"u8) || ns.SequenceEqual("System.Numerics"u8)) { parNode->simdTypeHnd = ObjectToHandle(type); if (parentIndex != uint.MaxValue) { #if READYTORUN if (NeedsTypeLayoutCheck(type)) { // We cannot allow the JIT to call getClassSize for // arbitrary types of fields as it will insert a fixup // that we may not be able to encode. We could skip the // field, but that will make prejit promotion different // from the runtime promotion. We could also change the // JIT to avoid calling getClassSize and just use the // size from the returned node, but for that we would // need to be sure that the type layout check fixup // added in getTypeLayout is sufficient to guarantee // the size of all these intrinsically handled SIMD // types. return GetTypeLayoutResult.Failure; } #endif return GetTypeLayoutResult.Success; } } } foreach (FieldDesc fd in type.GetFields()) { if (fd.IsStatic) continue; parNode->numFields++; Debug.Assert(fd.Offset != FieldAndOffset.InvalidOffset); TypeDesc fieldType = fd.FieldType; CorInfoType corInfoType = asCorInfoType(fieldType); if (corInfoType == CorInfoType.CORINFO_TYPE_VALUECLASS) { Debug.Assert(fieldType is MetadataType); GetTypeLayoutResult result = GetTypeLayoutHelper((MetadataType)fieldType, structNodeIndex, baseOffs + (uint)fd.Offset.AsInt, fd, treeNodes, maxTreeNodes, numTreeNodes); if (result != GetTypeLayoutResult.Success) return result; } else { if (*numTreeNodes >= maxTreeNodes) return GetTypeLayoutResult.Partial; CORINFO_TYPE_LAYOUT_NODE* treeNode = &treeNodes[(*numTreeNodes)++]; treeNode->simdTypeHnd = null; treeNode->diagFieldHnd = ObjectToHandle(fd); treeNode->parent = structNodeIndex; treeNode->offset = baseOffs + (uint)fd.Offset.AsInt; treeNode->size = (uint)fieldType.GetElementSize().AsInt; treeNode->numFields = 0; treeNode->type = corInfoType; treeNode->hasSignificantPadding = false; } if (type.IsInlineArray) { nuint treeNodeEnd = *numTreeNodes; int elemSize = fieldType.GetElementSize().AsInt; int arrSize = type.GetElementSize().AsInt; // Number of fields added for each element, including all // subfields. For example, for ValueTuple<int, int>[4]: // [ 0]: InlineArray parent = -1 // [ 1]: ValueTuple<int, int> parent = 0 - // [ 2]: int parent = 1 | // [ 3]: int parent = 1 | // [ 4]: ValueTuple<int, int> parent = 0 - stride = 3 // [ 5]: int parent = 4 // [ 6]: int parent = 4 // [ 7]: ValueTuple<int, int> parent = 0 // [ 8]: int parent = 7 // [ 9]: int parent = 7 // [10]: ValueTuple<int, int> parent = 0 // [11]: int parent = 10 // [12]: int parent = 10 uint elemFieldsStride = (uint)*numTreeNodes - (structNodeIndex + 1); // Now duplicate the fields of the previous entry for each // additional element. For each entry we have to update the // offset and the parent index. for (int elemOffset = elemSize; elemOffset < arrSize; elemOffset += elemSize) { nuint prevElemStart = *numTreeNodes - elemFieldsStride; for (nuint i = 0; i < elemFieldsStride; i++) { if (*numTreeNodes >= maxTreeNodes) return GetTypeLayoutResult.Partial; CORINFO_TYPE_LAYOUT_NODE* treeNode = &treeNodes[(*numTreeNodes)++]; *treeNode = treeNodes[prevElemStart + i]; treeNode->offset += (uint)elemSize; // The first field points back to the inline array // and has no bias; the rest of them do. treeNode->parent += (i == 0) ? 0 : elemFieldsStride; } parNode->numFields++; } } } return GetTypeLayoutResult.Success; } private GetTypeLayoutResult getTypeLayout(CORINFO_CLASS_STRUCT_* typeHnd, CORINFO_TYPE_LAYOUT_NODE* treeNodes, nuint* numTreeNodes) { TypeDesc type = HandleToObject(typeHnd); if (type is not MetadataType metadataType || !type.IsValueType) return GetTypeLayoutResult.Failure; nuint maxFields = *numTreeNodes; *numTreeNodes = 0; GetTypeLayoutResult result = GetTypeLayoutHelper(metadataType, uint.MaxValue, 0, null, treeNodes, maxFields, numTreeNodes); #if READYTORUN if (NeedsTypeLayoutCheck(type)) { ISymbolNode node = _compilation.SymbolNodeFactory.CheckTypeLayout(type); AddPrecodeFixup(node); } #endif return result; } private bool checkMethodModifier(CORINFO_METHOD_STRUCT_* hMethod, byte* modifier, bool fOptional) { throw new NotImplementedException("checkMethodModifier"); } private CorInfoHelpFunc getSharedCCtorHelper(CORINFO_CLASS_STRUCT_* clsHnd) { throw new NotImplementedException("getSharedCCtorHelper"); } private CORINFO_CLASS_STRUCT_* getTypeForBox(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); var typeForBox = type.IsNullable ? type.Instantiation[0] : type; return ObjectToHandle(typeForBox); } private CorInfoHelpFunc getBoxHelper(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); if (type.IsByRefLike) ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramSpecific, MethodBeingCompiled); return type.IsNullable ? CorInfoHelpFunc.CORINFO_HELP_BOX_NULLABLE : CorInfoHelpFunc.CORINFO_HELP_BOX; } private CorInfoHelpFunc getUnBoxHelper(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); return type.IsNullable ? CorInfoHelpFunc.CORINFO_HELP_UNBOX_NULLABLE : CorInfoHelpFunc.CORINFO_HELP_UNBOX; } private CorInfoInitClassResult initClass(CORINFO_FIELD_STRUCT_* field, CORINFO_METHOD_STRUCT_* method, CORINFO_CONTEXT_STRUCT* context) { FieldDesc fd = field == null ? null : HandleToObject(field); Debug.Assert(fd == null || fd.IsStatic); MethodDesc md = method == null ? MethodBeingCompiled : HandleToObject(method); TypeDesc type = fd != null ? fd.OwningType : typeFromContext(context); #if !READYTORUN if ( _isFallbackBodyCompilation || !_compilation.HasLazyStaticConstructor(type) ) { return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } #endif MetadataType typeToInit = (MetadataType)type; if (fd == null) { if (typeToInit.IsBeforeFieldInit) { // We can wait for field accesses to run .cctor return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } // Run .cctor on statics & constructors if (md.Signature.IsStatic) { // Except don't class construct on .cctor - it would be circular if (md.IsStaticConstructor) { return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } } else if (!md.IsConstructor && !typeToInit.IsValueType && !typeToInit.IsInterface) { // According to the spec, we should be able to do this optimization for both reference and valuetypes. // To maintain backward compatibility, we are doing it for reference types only. // We don't do this for interfaces though, as those don't have instance constructors. // For instance methods of types with precise-initialization // semantics, we can assume that the .ctor triggered the // type initialization. // This does not hold for NULL "this" object. However, the spec does // not require that case to work. return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } } if (typeToInit.IsCanonicalSubtype(CanonicalFormKind.Any)) { if (fd == null && method != null && context == contextFromMethodBeingCompiled()) { // If we're inling a call to a method in our own type, then we should already // have triggered the .cctor when caller was itself called. return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } // Shared generic code has to use helper. Moreover, tell JIT not to inline since // inlining of generic dictionary lookups is not supported. return CorInfoInitClassResult.CORINFO_INITCLASS_USE_HELPER | CorInfoInitClassResult.CORINFO_INITCLASS_DONT_INLINE; } // // Try to prove that the initialization is not necessary because of nesting // if (fd == null) { // Handled above Debug.Assert(!typeToInit.IsBeforeFieldInit); if (method != null && typeToInit == MethodBeingCompiled.OwningType) { // If we're inling a call to a method in our own type, then we should already // have triggered the .cctor when caller was itself called. return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } } else { // This optimization may cause static fields in reference types to be accessed without cctor being triggered // for NULL "this" object. It does not conform with what the spec says. However, we have been historically // doing it for perf reasons. if (!typeToInit.IsValueType && !typeToInit.IsInterface && !typeToInit.IsBeforeFieldInit) { if (typeToInit == typeFromContext(context) || typeToInit == MethodBeingCompiled.OwningType) { // The class will be initialized by the time we access the field. return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } } // If we are currently compiling the class constructor for this static field access then we can skip the initClass if (MethodBeingCompiled.OwningType == typeToInit && MethodBeingCompiled.IsStaticConstructor) { // The class will be initialized by the time we access the field. return CorInfoInitClassResult.CORINFO_INITCLASS_NOT_REQUIRED; } } return CorInfoInitClassResult.CORINFO_INITCLASS_USE_HELPER; } private CORINFO_CLASS_STRUCT_* getBuiltinClass(CorInfoClassId classId) { switch (classId) { case CorInfoClassId.CLASSID_SYSTEM_OBJECT: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.Object)); case CorInfoClassId.CLASSID_TYPED_BYREF: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.TypedReference)); case CorInfoClassId.CLASSID_TYPE_HANDLE: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.RuntimeTypeHandle)); case CorInfoClassId.CLASSID_FIELD_HANDLE: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.RuntimeFieldHandle)); case CorInfoClassId.CLASSID_METHOD_HANDLE: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.RuntimeMethodHandle)); case CorInfoClassId.CLASSID_ARGUMENT_HANDLE: ThrowHelper.ThrowTypeLoadException("System", "RuntimeArgumentHandle", _compilation.TypeSystemContext.SystemModule); return null; case CorInfoClassId.CLASSID_STRING: return ObjectToHandle(_compilation.TypeSystemContext.GetWellKnownType(WellKnownType.String)); case CorInfoClassId.CLASSID_RUNTIME_TYPE: return ObjectToHandle(_compilation.TypeSystemContext.SystemModule.GetKnownType("System"u8, "RuntimeType"u8)); default: throw new NotImplementedException(); } } private CorInfoType getTypeForPrimitiveValueClass(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); if (!type.IsPrimitive && !type.IsEnum) return CorInfoType.CORINFO_TYPE_UNDEF; return asCorInfoType(type); } private CorInfoType getTypeForPrimitiveNumericClass(CORINFO_CLASS_STRUCT_* cls) { var type = HandleToObject(cls); if (type.IsPrimitiveNumeric) return asCorInfoType(type); return CorInfoType.CORINFO_TYPE_UNDEF; } private bool canCast(CORINFO_CLASS_STRUCT_* child, CORINFO_CLASS_STRUCT_* parent) { throw new NotImplementedException("canCast"); } private TypeCompareState compareTypesForCast(CORINFO_CLASS_STRUCT_* fromClass, CORINFO_CLASS_STRUCT_* toClass) { TypeDesc fromType = HandleToObject(fromClass); TypeDesc toType = HandleToObject(toClass); TypeCompareState result = TypeCompareState.May; if (fromType.IsIDynamicInterfaceCastable) { result = TypeCompareState.May; } else if (toType.IsNullable) { // If casting to Nullable<T>, don't try to optimize result = TypeCompareState.May; } else if (!fromType.IsCanonicalSubtype(CanonicalFormKind.Any) && !toType.IsCanonicalSubtype(CanonicalFormKind.Any)) { // If the types are not shared, we can check directly. if (fromType.CanCastTo(toType)) result = TypeCompareState.Must; else result = TypeCompareState.MustNot; } else if (fromType.IsCanonicalSubtype(CanonicalFormKind.Any) && !toType.IsCanonicalSubtype(CanonicalFormKind.Any)) { // Casting from a shared type to an unshared type. // Only handle casts to interface types for now if (toType.IsInterface) { // Do a preliminary check. bool canCast = fromType.CanCastTo(toType); // Pass back positive results unfiltered. The unknown type // parameters in fromClass did not come into play. if (canCast) { result = TypeCompareState.Must; } // We have __Canon parameter(s) in fromClass, somewhere. // // In CanCastTo, these __Canon(s) won't match the interface or // instantiated types on the interface, so CanCastTo may // return false negatives. // // Only report MustNot if the fromClass is not __Canon // and the interface is not instantiated; then there is // no way for the fromClass __Canon(s) to confuse things. // // __Canon -> IBar May // IFoo<__Canon> -> IFoo<string> May // IFoo<__Canon> -> IBar MustNot // else if (fromType.IsCanonicalDefinitionType(CanonicalFormKind.Any)) { result = TypeCompareState.May; } else if (toType.HasInstantiation) { result = TypeCompareState.May; } else { result = TypeCompareState.MustNot; } } } #if READYTORUN // In R2R it is a breaking change for a previously positive // cast to become negative, but not for a previously negative // cast to become positive. So in R2R a negative result is // always reported back as May. if (result == TypeCompareState.MustNot) { result = TypeCompareState.May; } #endif return result; } private TypeCompareState compareTypesForEquality(CORINFO_CLASS_STRUCT_* cls1, CORINFO_CLASS_STRUCT_* cls2) { TypeDesc type1 = HandleToObject(cls1); TypeDesc type2 = HandleToObject(cls2); return TypeExtensions.CompareTypesForEquality(type1, type2) switch { true => TypeCompareState.Must, false => TypeCompareState.MustNot, _ => TypeCompareState.May, }; } private bool isMoreSpecificType(CORINFO_CLASS_STRUCT_* cls1, CORINFO_CLASS_STRUCT_* cls2) { TypeDesc type1 = HandleToObject(cls1); TypeDesc type2 = HandleToObject(cls2); // If both types have the same type definition while // hnd1 is shared and hnd2 is not - consider hnd2 more specific. if (type1.HasSameTypeDefinition(type2)) { return type1.IsCanonicalSubtype(CanonicalFormKind.Any) && !type2.IsCanonicalSubtype(CanonicalFormKind.Any); } // Look for a common parent type. TypeDesc merged = TypeExtensions.MergeTypesToCommonParent(type1, type2); // If the common parent is type1, then type2 is more specific. return merged == type1; } private bool isExactType(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); while (type.IsArray) { ArrayType arrayType = (ArrayType)type; // Single dimensional array with non-zero bounds may be SZ array. if (arrayType.IsMdArray && arrayType.Rank == 1) return false; type = arrayType.ElementType; // Arrays of primitives are interchangeable with arrays of enums of the same underlying type. if (type.IsPrimitive || type.IsEnum) return false; } // Use conservative answer for pointers and custom types. if (!type.IsDefType) return false; // Use conservative answer for equivalent and variant types. if (type.HasTypeEquivalence || type.HasVariance) return false; // SZArrayHelper (CoreCLR) and Array<T> (NativeAOT) are phantom dispatch // targets: the runtime maps generic array interface methods // (IList<T>.set_Item, etc.) to methods on these sealed/effectively-sealed // types, but no runtime object ever has them as its MethodTable - `this` // inside those methods is always a T[]. Reporting them as exact would // allow the JIT (e.g. VN-based invariant-load folding of GetMethodTable) // to embed their MT as a constant and mis-read fields off it. Both types // are marked [Intrinsic] so that the cheap flag check filters out // non-candidates before we compare names. if (type.IsIntrinsic && type is MetadataType mdType) { ReadOnlySpan<byte> name = mdType.Name; if ((name.SequenceEqual("SZArrayHelper"u8) || name.SequenceEqual("Array`1"u8)) && mdType.Namespace.SequenceEqual("System"u8)) { return false; } } // Valuetypes are invariant. This assumes that introducing type equivalence to an existing type // is not compatible change. if (type.IsValueType) return true; return _compilation.IsEffectivelySealed(type); } private TypeCompareState isGenericType(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); if (type.IsCanonicalDefinitionType(CanonicalFormKind.Any)) { return TypeCompareState.May; } return type.HasInstantiation ? TypeCompareState.Must : TypeCompareState.MustNot; } private TypeCompareState isNullableType(CORINFO_CLASS_STRUCT_* cls) { TypeDesc type = HandleToObject(cls); Debug.Assert(!type.IsGenericParameter); return type.IsNullable ? TypeCompareState.Must : TypeCompareState.MustNot; } private TypeCompareState isEnum(CORINFO_CLASS_STRUCT_* cls, CORINFO_CLASS_STRUCT_** underlyingType) { Debug.Assert(cls != null); if (underlyingType != null) { *underlyingType = null; } TypeDesc type = HandleToObject(cls); Debug.Assert(!type.IsGenericParameter); if (type.IsEnum) { if (underlyingType != null) { *underlyingType = ObjectToHandle(type.UnderlyingType); } return TypeCompareState.Must; } else { return TypeCompareState.MustNot; } } private CORINFO_CLASS_STRUCT_* getParentType(CORINFO_CLASS_STRUCT_* cls) { throw new NotImplementedException("getParentType"); } private CorInfoType getChildType(CORINFO_CLASS_STRUCT_* clsHnd, CORINFO_CLASS_STRUCT_** clsRet) { CorInfoType result = CorInfoType.CORINFO_TYPE_UNDEF; var td = HandleToObject(clsHnd); if (td.IsArray || td.IsByRef || td.IsPointer) { TypeDesc returnType = ((ParameterizedType)td).ParameterType; result = asCorInfoType(returnType, clsRet); } else { *clsRet = null; } return result; } private bool isSDArray(CORINFO_CLASS_STRUCT_* cls) { var td = HandleToObject(cls); return td.IsSzArray; } private uint getArrayRank(CORINFO_CLASS_STRUCT_* cls) { uint rank = 0; var td = HandleToObject(cls) as ArrayType; if (td != null) { rank = (uint)td.Rank; } return rank; } private CorInfoArrayIntrinsic getArrayIntrinsicID(CORINFO_METHOD_STRUCT_* ftn) { CorInfoArrayIntrinsic kind = CorInfoArrayIntrinsic.ILLEGAL; if (HandleToObject(ftn) is ArrayMethod am) { kind = am.Kind switch { ArrayMethodKind.Get => CorInfoArrayIntrinsic.GET, ArrayMethodKind.Set => CorInfoArrayIntrinsic.SET, ArrayMethodKind.Address => CorInfoArrayIntrinsic.ADDRESS, _ => CorInfoArrayIntrinsic.ILLEGAL }; } return kind; } private void* getArrayInitializationData(CORINFO_FIELD_STRUCT_* field, uint size) { var fd = HandleToObject(field); // Check for invalid arguments passed to InitializeArray intrinsic if (!fd.HasRva || size > fd.FieldType.GetElementSize().AsInt) { return null; } return (void*)ObjectToHandle(_compilation.GetFieldRvaData(fd)); } #pragma warning disable CA1822 // Mark members as static private CorInfoIsAccessAllowedResult canAccessClass(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_METHOD_STRUCT_* callerHandle, ref CORINFO_HELPER_DESC pAccessHelper) #pragma warning restore CA1822 // Mark members as static { // TODO: Access check return CorInfoIsAccessAllowedResult.CORINFO_ACCESS_ALLOWED; } private nuint printFieldName(CORINFO_FIELD_STRUCT_* fld, byte* buffer, nuint bufferSize, nuint* requiredBufferSize) { FieldDesc field = HandleToObject(fld); return PrintFromUtf16(field.GetName(), buffer, bufferSize, requiredBufferSize); } #pragma warning disable CA1822 // Mark members as static private uint getThreadLocalFieldInfo(CORINFO_FIELD_STRUCT_* fld, bool isGCType) #pragma warning restore CA1822 // Mark members as static { // Implemented for JIT only for now. return 0; } #pragma warning disable CA1822 // Mark members as static private void getThreadLocalStaticBlocksInfo(CORINFO_THREAD_STATIC_BLOCKS_INFO* pInfo) #pragma warning restore CA1822 // Mark members as static { // Implemented for JIT only for now. } private CORINFO_CLASS_STRUCT_* getFieldClass(CORINFO_FIELD_STRUCT_* field) { var fieldDesc = HandleToObject(field); return ObjectToHandle(fieldDesc.OwningType); } private CorInfoType getFieldType(CORINFO_FIELD_STRUCT_* field, CORINFO_CLASS_STRUCT_** structType, CORINFO_CLASS_STRUCT_* fieldOwnerHint) { FieldDesc fieldDesc = HandleToObject(field); TypeDesc fieldType = fieldDesc.FieldType; CorInfoType type; if (structType != null) { type = asCorInfoType(fieldType, structType); } else { type = asCorInfoType(fieldType); } return type; } private uint getFieldOffset(CORINFO_FIELD_STRUCT_* field) { var fieldDesc = HandleToObject(field); Debug.Assert(fieldDesc.Offset != FieldAndOffset.InvalidOffset); return (uint)fieldDesc.Offset.AsInt; } private static CORINFO_FIELD_ACCESSOR getFieldIntrinsic(FieldDesc field) { Debug.Assert(field.IsIntrinsic); var owningType = field.OwningType; if ((owningType.IsWellKnownType(WellKnownType.IntPtr) || owningType.IsWellKnownType(WellKnownType.UIntPtr)) && field.Name.SequenceEqual("Zero"u8)) { return CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INTRINSIC_ZERO; } else if (owningType.IsString && field.Name.SequenceEqual("Empty"u8)) { return CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INTRINSIC_EMPTY_STRING; } else if (owningType.Name.SequenceEqual("BitConverter"u8) && owningType.Namespace.SequenceEqual("System"u8) && field.Name.SequenceEqual("IsLittleEndian"u8)) { return CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INTRINSIC_ISLITTLEENDIAN; } return (CORINFO_FIELD_ACCESSOR)(-1); } private bool isFieldStatic(CORINFO_FIELD_STRUCT_* fldHnd) { return HandleToObject(fldHnd).IsStatic; } private void getBoundaries(CORINFO_METHOD_STRUCT_* ftn, ref uint cILOffsets, ref uint* pILOffsets, BoundaryTypes* implicitBoundaries) { // TODO: Debugging cILOffsets = 0; pILOffsets = null; *implicitBoundaries = BoundaryTypes.DEFAULT_BOUNDARIES; } private void getVars(CORINFO_METHOD_STRUCT_* ftn, ref uint cVars, ILVarInfo** vars, ref bool extendOthers) { // TODO: Debugging cVars = 0; *vars = null; // Just tell the JIT to extend everything. extendOthers = true; } #pragma warning disable CA1822 // Mark members as static private void reportRichMappings(InlineTreeNode* inlineTree, uint numInlineTree, RichOffsetMapping* mappings, uint numMappings) #pragma warning restore CA1822 // Mark members as static { NativeMemory.Free(inlineTree); NativeMemory.Free(mappings); } #pragma warning disable CA1822 // Mark members as static private void reportAsyncDebugInfo(AsyncInfo* asyncInfo, AsyncSuspensionPoint* suspensionPoints, AsyncContinuationVarInfo* vars, uint numVars) #pragma warning restore CA1822 // Mark members as static { NativeMemory.Free(suspensionPoints); NativeMemory.Free(vars); } #pragma warning disable CA1822 // Mark members as static private void reportMetadata(byte* key, void* value, nuint length) #pragma warning restore CA1822 // Mark members as static { } #pragma warning disable CA1822 // Mark members as static private void* allocateArray(nuint cBytes) #pragma warning restore CA1822 // Mark members as static { return NativeMemory.Alloc(cBytes); } #pragma warning disable CA1822 // Mark members as static private void freeArray(void* array) #pragma warning restore CA1822 // Mark members as static { NativeMemory.Free(array); } #pragma warning disable CA1822 // Mark members as static private CORINFO_ARG_LIST_STRUCT_* getArgNext(CORINFO_ARG_LIST_STRUCT_* args) #pragma warning restore CA1822 // Mark members as static { return (CORINFO_ARG_LIST_STRUCT_*)((int)args + 1); } private CorInfoTypeWithMod getArgType(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_STRUCT_* args, CORINFO_CLASS_STRUCT_** vcTypeRet) { int index = (int)args; object sigObj = HandleToObject((void*)sig->methodSignature); MethodSignature methodSig = sigObj as MethodSignature; if (methodSig != null) { TypeDesc type = methodSig[index]; CorInfoType corInfoType = asCorInfoType(type, vcTypeRet); return (CorInfoTypeWithMod)corInfoType; } else { LocalVariableDefinition[] locals = (LocalVariableDefinition[])sigObj; TypeDesc type = locals[index].Type; CorInfoType corInfoType = asCorInfoType(type, vcTypeRet); return (CorInfoTypeWithMod)corInfoType | (locals[index].IsPinned ? CorInfoTypeWithMod.CORINFO_TYPE_MOD_PINNED : 0); } } private CORINFO_CLASS_STRUCT_* getArgClass(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_STRUCT_* args) { int index = (int)args; object sigObj = HandleToObject((void*)sig->methodSignature); MethodSignature methodSig = sigObj as MethodSignature; if (methodSig != null) { TypeDesc type = methodSig[index]; return ObjectToHandle(type); } else { LocalVariableDefinition[] locals = (LocalVariableDefinition[])sigObj; TypeDesc type = locals[index].Type; return ObjectToHandle(type); } } private CorInfoHFAElemType getHFAType(CORINFO_CLASS_STRUCT_* hClass) { var type = (DefType)HandleToObject(hClass); // See MethodTable::GetHFAType and Compiler::GetHfaType. return (type.ValueTypeShapeCharacteristics & ValueTypeShapeCharacteristics.AggregateMask) switch { ValueTypeShapeCharacteristics.Float32Aggregate => CorInfoHFAElemType.CORINFO_HFA_ELEM_FLOAT, ValueTypeShapeCharacteristics.Float64Aggregate => CorInfoHFAElemType.CORINFO_HFA_ELEM_DOUBLE, ValueTypeShapeCharacteristics.Vector64Aggregate => CorInfoHFAElemType.CORINFO_HFA_ELEM_VECTOR64, ValueTypeShapeCharacteristics.Vector128Aggregate => CorInfoHFAElemType.CORINFO_HFA_ELEM_VECTOR128, _ => CorInfoHFAElemType.CORINFO_HFA_ELEM_NONE }; } #pragma warning disable CA1822 // Mark members as static private bool runWithErrorTrap(void* function, void* parameter) #pragma warning restore CA1822 // Mark members as static { // This method is completely handled by the C++ wrapper to the JIT-EE interface, // and should never reach the managed implementation. Debug.Fail("CorInfoImpl.runWithErrorTrap should not be called"); throw new NotSupportedException("runWithErrorTrap"); } #pragma warning disable CA1822 // Mark members as static private bool runWithSPMIErrorTrap(void* function, void* parameter) #pragma warning restore CA1822 // Mark members as static { // This method is completely handled by the C++ wrapper to the JIT-EE interface, // and should never reach the managed implementation. Debug.Fail("CorInfoImpl.runWithSPMIErrorTrap should not be called"); throw new NotSupportedException("runWithSPMIErrorTrap"); } public static CORINFO_OS TargetToOs(TargetDetails target) { return target.IsWindows ? CORINFO_OS.CORINFO_WINNT : target.IsApplePlatform ? CORINFO_OS.CORINFO_APPLE : CORINFO_OS.CORINFO_UNIX; } private void getEEInfo(ref CORINFO_EE_INFO pEEInfoOut) { pEEInfoOut = default(CORINFO_EE_INFO); #if DEBUG // In debug, write some bogus data to the struct to ensure we have filled everything // properly. fixed (CORINFO_EE_INFO* tmp = &pEEInfoOut) NativeMemory.Fill(tmp, (nuint)sizeof(CORINFO_EE_INFO), 0xcc); #endif int pointerSize = this.PointerSize; pEEInfoOut.inlinedCallFrameInfo.size = (uint)SizeOfPInvokeTransitionFrame; pEEInfoOut.offsetOfDelegateInstance = (uint)pointerSize; // Delegate::_firstParameter pEEInfoOut.offsetOfDelegateFirstTarget = OffsetOfDelegateFirstTarget; pEEInfoOut.sizeOfReversePInvokeFrame = (uint)SizeOfReversePInvokeTransitionFrame; pEEInfoOut.osPageSize = 0x1000; if (_compilation.NodeFactory.Target.IsWasm) { // TODO: Set this value to 0 for Wasm pEEInfoOut.maxUncheckedOffsetForNullObject = 1024 - 1; } else if (_compilation.NodeFactory.Target.IsWindows) { pEEInfoOut.maxUncheckedOffsetForNullObject = 32 * 1024 - 1; } else { pEEInfoOut.maxUncheckedOffsetForNullObject = pEEInfoOut.osPageSize / 2 - 1; } pEEInfoOut.targetAbi = TargetABI; pEEInfoOut.osType = TargetToOs(_compilation.NodeFactory.Target); } private void getAsyncInfo(ref CORINFO_ASYNC_INFO pAsyncInfoOut) { DefType continuation = _compilation.TypeSystemContext.ContinuationType; pAsyncInfoOut.continuationClsHnd = ObjectToHandle(continuation); pAsyncInfoOut.continuationNextFldHnd = ObjectToHandle(continuation.GetKnownField("Next"u8)); pAsyncInfoOut.continuationResumeInfoFldHnd = ObjectToHandle(continuation.GetKnownField("ResumeInfo"u8)); pAsyncInfoOut.continuationStateFldHnd = ObjectToHandle(continuation.GetKnownField("State"u8)); pAsyncInfoOut.continuationFlagsFldHnd = ObjectToHandle(continuation.GetKnownField("Flags"u8)); DefType asyncHelpers = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices"u8, "AsyncHelpers"u8); pAsyncInfoOut.captureExecutionContextMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("CaptureExecutionContext"u8, null)); pAsyncInfoOut.captureContinuationContextMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("CaptureContinuationContext"u8, null)); pAsyncInfoOut.captureContextsMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("CaptureContexts"u8, null)); pAsyncInfoOut.restoreContextsMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("RestoreContexts"u8, null)); pAsyncInfoOut.restoreContextsOnSuspensionMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("RestoreContextsOnSuspension"u8, null)); pAsyncInfoOut.finishSuspensionNoContinuationContextMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("FinishSuspensionNoContinuationContext"u8, null)); pAsyncInfoOut.finishSuspensionWithContinuationContextMethHnd = ObjectToHandle(asyncHelpers.GetKnownMethod("FinishSuspensionWithContinuationContext"u8, null)); } private CORINFO_CLASS_STRUCT_* getContinuationType(nuint dataSize, ref bool objRefs, nuint objRefsSize) { Debug.Assert(objRefsSize == (dataSize + (nuint)(PointerSize - 1)) / (nuint)PointerSize); GCPointerMapBuilder gcMapBuilder = new GCPointerMapBuilder((int)dataSize, PointerSize); ReadOnlySpan<bool> bools = MemoryMarshal.CreateReadOnlySpan(ref objRefs, (int)objRefsSize); for (int i = 0; i < bools.Length; i++) { if (bools[i]) gcMapBuilder.MarkGCPointer(i * PointerSize); } return ObjectToHandle(_compilation.TypeSystemContext.GetContinuationType(gcMapBuilder.ToGCMap())); } private mdToken getMethodDefFromMethod(CORINFO_METHOD_STRUCT_* hMethod) { MethodDesc method = HandleToObject(hMethod); #if READYTORUN if (method is UnboxingMethodDesc unboxingMethodDesc) { method = unboxingMethodDesc.Target; } #endif MethodDesc methodDefinition = method.GetTypicalMethodDefinition(); // Need to cast down to EcmaMethod. Do not use this as a precedent that casting to Ecma* // within the JitInterface is fine. We might want to consider moving this to Compilation. TypeSystem.Ecma.EcmaMethod ecmaMethodDefinition = methodDefinition as TypeSystem.Ecma.EcmaMethod; if (ecmaMethodDefinition != null) { return (mdToken)System.Reflection.Metadata.Ecma335.MetadataTokens.GetToken(ecmaMethodDefinition.Handle); } return 0; } private static byte[] StringToUTF8(string s) { int byteCount = Encoding.UTF8.GetByteCount(s); byte[] bytes = new byte[byteCount + 1]; Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); return bytes; } private static byte[] SpanToPinnableBytes(ReadOnlySpan<byte> s) { byte[] bytes = new byte[s.Length + 1]; s.CopyTo(bytes); return bytes; } private nuint printMethodName(CORINFO_METHOD_STRUCT_* ftn, byte* buffer, nuint bufferSize, nuint* requiredBufferSize) { MethodDesc method = HandleToObject(ftn); return PrintFromUtf16(method.GetName(), buffer, bufferSize, requiredBufferSize); } private static string getMethodNameFromMetadataImpl(MethodDesc method, out string className, out string namespaceName, string[] enclosingClassName) { className = null; namespaceName = null; string result = method.GetName(); MetadataType owningType = method.OwningType as MetadataType; if (owningType != null) { className = owningType.GetName(); namespaceName = owningType.GetNamespace(); // Query enclosingClassName when the method is in a nested class // and get the namespace of enclosing classes (nested class's namespace is empty) DefType containingType = owningType; for (int i = 0; i < enclosingClassName.Length; i++) { containingType = containingType.ContainingType; if (containingType == null) break; enclosingClassName[i] = containingType.GetName(); namespaceName = containingType.GetNamespace(); } } return result; } private byte* getMethodNameFromMetadata(CORINFO_METHOD_STRUCT_* ftn, byte** className, byte** namespaceName, byte** enclosingClassNames, nuint maxEnclosingClassNames) { MethodDesc method = HandleToObject(ftn); for (nuint i = 0; i < maxEnclosingClassNames; i++) enclosingClassNames[i] = null; if (method.GetTypicalMethodDefinition() is EcmaMethod ecmaMethod) { EcmaType owningType = ecmaMethod.OwningType; var reader = owningType.MetadataReader; if (className != null) *className = reader.GetTypeNamePointer(owningType.Handle); if (namespaceName != null) *namespaceName = reader.GetTypeNamespacePointer(owningType.Handle); // Query enclosingClassName when the method is in a nested class // and get the namespace of enclosing classes (nested class's namespace is empty) EcmaType containingType = owningType; for (nuint i = 0; i < maxEnclosingClassNames; i++) { containingType = containingType.ContainingType; if (containingType == null) break; enclosingClassNames[i] = reader.GetTypeNamePointer(containingType.Handle); if (namespaceName != null) *namespaceName = reader.GetTypeNamespacePointer(containingType.Handle); } return reader.GetMethodNamePointer(ecmaMethod.Handle); } else { string result; string classResult; string namespaceResult; string[] enclosingResults = new string[maxEnclosingClassNames]; result = getMethodNameFromMetadataImpl(method, out classResult, out namespaceResult, enclosingResults); if (className != null) *className = classResult != null ? (byte*)GetPin(StringToUTF8(classResult)) : null; if (namespaceName != null) *namespaceName = namespaceResult != null ? (byte*)GetPin(StringToUTF8(namespaceResult)) : null; for (nuint i = 0; i < maxEnclosingClassNames; i++) enclosingClassNames[i] = enclosingResults[i] != null ? (byte*)GetPin(StringToUTF8(enclosingResults[i])) : null; return result != null ? (byte*)GetPin(StringToUTF8(result)) : null; } } private uint getMethodHash(CORINFO_METHOD_STRUCT_* ftn) { return (uint)HandleToObject(ftn).GetHashCode(); } private bool getSystemVAmd64PassStructInRegisterDescriptor(CORINFO_CLASS_STRUCT_* structHnd, SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR* structPassInRegDescPtr) { TypeDesc typeDesc = HandleToObject(structHnd); SystemVStructClassificator.GetSystemVAmd64PassStructInRegisterDescriptor(typeDesc, out *structPassInRegDescPtr); return true; } private void getSwiftLowering(CORINFO_CLASS_STRUCT_* structHnd, ref CORINFO_SWIFT_LOWERING lowering) { lowering = SwiftPhysicalLowering.LowerTypeForSwiftSignature(HandleToObject(structHnd)); } private void getFpStructLowering(CORINFO_CLASS_STRUCT_* structHnd, ref CORINFO_FPSTRUCT_LOWERING lowering) { FpStructInRegistersInfo info = RiscVLoongArch64FpStruct.GetFpStructInRegistersInfo( HandleToObject(structHnd), _compilation.TypeSystemContext.Target.Architecture); if (info.flags != FpStruct.UseIntCallConv) { lowering.byIntegerCallConv = false; lowering.Offsets[0] = info.offset1st; lowering.Offsets[1] = info.offset2nd; lowering.numLoweredElements = ((info.flags & FpStruct.OnlyOne) != 0) ? 1 : 2; if ((info.flags & (FpStruct.BothFloat | FpStruct.FloatInt | FpStruct.OnlyOne)) != 0) lowering.LoweredElements[0] = (info.SizeShift1st() == 3) ? CorInfoType.CORINFO_TYPE_DOUBLE : CorInfoType.CORINFO_TYPE_FLOAT; if ((info.flags & (FpStruct.BothFloat | FpStruct.IntFloat)) != 0) lowering.LoweredElements[1] = (info.SizeShift2nd() == 3) ? CorInfoType.CORINFO_TYPE_DOUBLE : CorInfoType.CORINFO_TYPE_FLOAT; if ((info.flags & (FpStruct.FloatInt | FpStruct.IntFloat)) != 0) { int index = ((info.flags & FpStruct.FloatInt) != 0) ? 1 : 0; uint sizeShift = (index == 0) ? info.SizeShift1st() : info.SizeShift2nd(); lowering.LoweredElements[index] = (CorInfoType)((int)CorInfoType.CORINFO_TYPE_BYTE + sizeShift * 2); // unittests Debug.Assert((int)CorInfoType.CORINFO_TYPE_BYTE + 0 * 2 == (int)CorInfoType.CORINFO_TYPE_BYTE); Debug.Assert((int)CorInfoType.CORINFO_TYPE_BYTE + 1 * 2 == (int)CorInfoType.CORINFO_TYPE_SHORT); Debug.Assert((int)CorInfoType.CORINFO_TYPE_BYTE + 2 * 2 == (int)CorInfoType.CORINFO_TYPE_INT); Debug.Assert((int)CorInfoType.CORINFO_TYPE_BYTE + 3 * 2 == (int)CorInfoType.CORINFO_TYPE_LONG); } } else { lowering.byIntegerCallConv = true; } } private CorInfoWasmType getWasmLowering(CORINFO_CLASS_STRUCT_* structHnd) { TypeDesc type = HandleToObject(structHnd); #if READYTORUN LayoutInt classSize = type.GetElementSize(); if (classSize.IsIndeterminate) { throw new RequiresRuntimeJitException(type); } if (NeedsTypeLayoutCheck(type)) { ISymbolNode node = _compilation.SymbolNodeFactory.CheckTypeLayout(type); AddPrecodeFixup(node); } #endif TypeDesc abiType = WasmLowering.LowerToAbiType(type); if (abiType == null) { // Indicate type should be passed by-ref. return CorInfoWasmType.CORINFO_WASM_TYPE_VOID; } WasmValueType wasmAbiType = WasmLowering.LowerType(abiType); switch (wasmAbiType) { case WasmValueType.I32: return CorInfoWasmType.CORINFO_WASM_TYPE_I32; case WasmValueType.I64: return CorInfoWasmType.CORINFO_WASM_TYPE_I64; case WasmValueType.F32: return CorInfoWasmType.CORINFO_WASM_TYPE_F32; case WasmValueType.F64: return CorInfoWasmType.CORINFO_WASM_TYPE_F64; default: ThrowHelper.ThrowInvalidProgramException(); return CorInfoWasmType.CORINFO_WASM_TYPE_I32; // unreachable } } private uint getThreadTLSIndex(ref void* ppIndirection) { throw new NotImplementedException("getThreadTLSIndex"); } private Dictionary<CorInfoHelpFunc, ISymbolNode> _helperCache = new Dictionary<CorInfoHelpFunc, ISymbolNode>(); private void getHelperFtn(CorInfoHelpFunc ftnNum, CORINFO_CONST_LOOKUP *pNativeEntrypoint, CORINFO_METHOD_STRUCT_** pMethod) { // We never return a method handle from the managed implementation of this method today if (pMethod != null) *pMethod = null; if (pNativeEntrypoint != null) { ISymbolNode entryPoint; if (!_helperCache.TryGetValue(ftnNum, out entryPoint)) { entryPoint = GetHelperFtnUncached(ftnNum); _helperCache.Add(ftnNum, entryPoint); } if (entryPoint.RepresentsIndirectionCell) { pNativeEntrypoint->addr = (void*)ObjectToHandle(entryPoint); pNativeEntrypoint->accessType = InfoAccessType.IAT_PVALUE; } else { pNativeEntrypoint->addr = (void*)ObjectToHandle(entryPoint); pNativeEntrypoint->accessType = InfoAccessType.IAT_VALUE; } } } public static ReadyToRunHelperId GetReadyToRunHelperFromStaticBaseHelper(CorInfoHelpFunc helper) { ReadyToRunHelperId res; switch (helper) { case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_GCSTATIC_BASE: res = ReadyToRunHelperId.GetGCStaticBase; break; case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NONGCSTATIC_BASE: res = ReadyToRunHelperId.GetNonGCStaticBase; break; case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_THREADSTATIC_BASE: res = ReadyToRunHelperId.GetThreadStaticBase; break; case CorInfoHelpFunc.CORINFO_HELP_READYTORUN_NONGCTHREADSTATIC_BASE: res = ReadyToRunHelperId.GetThreadNonGcStaticBase; break; default: throw new NotImplementedException("ReadyToRun: " + helper.ToString()); } return res; } private void getFunctionFixedEntryPoint(CORINFO_METHOD_STRUCT_* ftn, bool isUnsafeFunctionPointer, ref CORINFO_CONST_LOOKUP pResult) { throw new NotImplementedException("getFunctionFixedEntryPoint"); } private CORINFO_MODULE_STRUCT_* embedModuleHandle(CORINFO_MODULE_STRUCT_* handle, ref void* ppIndirection) { throw new NotImplementedException("embedModuleHandle"); } private CORINFO_FIELD_STRUCT_* embedFieldHandle(CORINFO_FIELD_STRUCT_* handle, ref void* ppIndirection) { throw new NotImplementedException("embedFieldHandle"); } private static CORINFO_RUNTIME_LOOKUP_KIND GetGenericRuntimeLookupKind(MethodDesc method) { if (method.RequiresInstMethodDescArg()) return CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_METHODPARAM; else if (method.RequiresInstMethodTableArg()) return CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_CLASSPARAM; else { Debug.Assert(method.AcquiresInstMethodTableFromThis()); return CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_THISOBJ; } } private void getLocationOfThisType(CORINFO_METHOD_STRUCT_* context, ref CORINFO_LOOKUP_KIND result) { MethodDesc method = HandleToObject(context); if (method.IsSharedByGenericInstantiations) { result.needsRuntimeLookup = true; result.runtimeLookupKind = GetGenericRuntimeLookupKind(method); } else { result.needsRuntimeLookup = false; result.runtimeLookupKind = CORINFO_RUNTIME_LOOKUP_KIND.CORINFO_LOOKUP_THISOBJ; } } private void* GetCookieForInterpreterCalliSig(CORINFO_SIG_INFO* szMetaSig) { throw new NotImplementedException("GetCookieForInterpreterCalliSig"); } private void* GetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig, ref void* ppIndirection) { #if READYTORUN throw new RequiresRuntimeJitException($"{MethodBeingCompiled} -> {nameof(GetCookieForPInvokeCalliSig)}"); #else throw new NotImplementedException(nameof(GetCookieForPInvokeCalliSig)); #endif } #pragma warning disable CA1822 // Mark members as static private CORINFO_JUST_MY_CODE_HANDLE_* getJustMyCodeHandle(CORINFO_METHOD_STRUCT_* method, ref CORINFO_JUST_MY_CODE_HANDLE_* ppIndirection) #pragma warning restore CA1822 // Mark members as static { ppIndirection = null; return null; } private void GetProfilingHandle(ref bool pbHookFunction, ref void* pProfilerHandle, ref bool pbIndirectedHandles) { throw new NotImplementedException("GetProfilingHandle"); } /// <summary> /// Create a CORINFO_CONST_LOOKUP to a symbol and put the address into the addr field /// </summary> private CORINFO_CONST_LOOKUP CreateConstLookupToSymbol(ISymbolNode symbol) { CORINFO_CONST_LOOKUP constLookup = default(CORINFO_CONST_LOOKUP); constLookup.addr = (void*)ObjectToHandle(symbol); constLookup.accessType = symbol.RepresentsIndirectionCell ? InfoAccessType.IAT_PVALUE : InfoAccessType.IAT_VALUE; return constLookup; } private CORINFO_CLASS_STRUCT_* getStaticFieldCurrentClass(CORINFO_FIELD_STRUCT_* field, byte* pIsSpeculative) { if (pIsSpeculative != null) *pIsSpeculative = 1; return null; } private IntPtr getVarArgsHandle(CORINFO_SIG_INFO* pSig, CORINFO_METHOD_STRUCT_* methHnd, ref void* ppIndirection) { throw new NotImplementedException("getVarArgsHandle"); } private InfoAccessType emptyStringLiteral(ref void* ppValue) { return constructStringLiteral(_methodScope, (mdToken)CorTokenType.mdtString, ref ppValue); } private uint getFieldThreadLocalStoreID(CORINFO_FIELD_STRUCT_* field, ref void* ppIndirection) { throw new NotImplementedException("getFieldThreadLocalStoreID"); } private CORINFO_METHOD_STRUCT_* GetDelegateCtor(CORINFO_METHOD_STRUCT_* methHnd, CORINFO_CLASS_STRUCT_* clsHnd, CORINFO_METHOD_STRUCT_* targetMethodHnd, ref DelegateCtorArgs pCtorData) { throw new NotImplementedException("GetDelegateCtor"); } private void MethodCompileComplete(CORINFO_METHOD_STRUCT_* methHnd) { throw new NotImplementedException("MethodCompileComplete"); } #pragma warning disable CA1822 // Mark members as static private bool getTailCallHelpers(ref CORINFO_RESOLVED_TOKEN callToken, CORINFO_SIG_INFO* sig, CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, ref CORINFO_TAILCALL_HELPERS pResult) #pragma warning restore CA1822 // Mark members as static { // Slow tailcalls are not supported yet // https://github.com/dotnet/runtime/issues/35423 #if READYTORUN throw new RequiresRuntimeJitException(nameof(getTailCallHelpers)); #else return false; #endif } private byte[] _code; private byte[] _coldCode; private int _codeAlignment; private byte[] _roData; private MethodReadOnlyDataNode _roDataBlob; private int _roDataAlignment; private byte[] _rwData; private MethodReadWriteDataNode _rwDataBlob; private int _rwDataAlignment; private int _numFrameInfos; private int _usedFrameInfos; private FrameInfo[] _frameInfos; #if READYTORUN private int _numColdFrameInfos; private int _usedColdFrameInfos; private FrameInfo[] _coldFrameInfos; #endif private byte[] _gcInfo; private CORINFO_EH_CLAUSE[] _ehClauses; private DependencyList _additionalDependencies; private void allocMem(ref AllocMemArgs args) { Span<AllocMemChunk> chunks = new Span<AllocMemChunk>(args.chunks, checked((int)args.chunksCount)); uint roDataSize = 0; uint rwDataSize = 0; _codeAlignment = -1; _roDataAlignment = -1; _rwDataAlignment = -1; // ELF/MachO do not support relocations from read-only sections to code sections, so we must put these // into read-write sections. bool ChunkNeedsReadWriteSection(in AllocMemChunk chunk) => (chunk.flags & CorJitAllocMemFlag.CORJIT_ALLOCMEM_HAS_POINTERS_TO_CODE) != 0 && !_compilation.TypeSystemContext.Target.IsWindows; foreach (ref AllocMemChunk chunk in chunks) { if ((chunk.flags & CorJitAllocMemFlag.CORJIT_ALLOCMEM_HOT_CODE) != 0) { chunk.block = (byte*)GetPin(_code = new byte[chunk.size]); chunk.blockRW = chunk.block; _codeAlignment = (int)chunk.alignment; } else if ((chunk.flags & CorJitAllocMemFlag.CORJIT_ALLOCMEM_COLD_CODE) != 0) { chunk.block = (byte*)GetPin(_coldCode = new byte[chunk.size]); chunk.blockRW = chunk.block; Debug.Assert(chunk.alignment == 1); #if READYTORUN _methodColdCodeNode = new MethodColdCodeNode(MethodBeingCompiled); #endif } else if (ChunkNeedsReadWriteSection(chunk)) { // For ELF/MachO we need to put data containing relocations into .text into a RW section rwDataSize = (uint)((int)rwDataSize).AlignUp((int)chunk.alignment); rwDataSize += chunk.size; _rwDataAlignment = Math.Max(_rwDataAlignment, (int)chunk.alignment); } else { roDataSize = (uint)((int)roDataSize).AlignUp((int)chunk.alignment); roDataSize += chunk.size; _roDataAlignment = Math.Max(_roDataAlignment, (int)chunk.alignment); } } if (roDataSize != 0) { _roData = new byte[roDataSize]; _roDataBlob = new MethodReadOnlyDataNode(MethodBeingCompiled); byte* roDataBlock = (byte*)GetPin(_roData); int offset = 0; foreach (ref AllocMemChunk chunk in chunks) { if ((chunk.flags & (CorJitAllocMemFlag.CORJIT_ALLOCMEM_HOT_CODE | CorJitAllocMemFlag.CORJIT_ALLOCMEM_COLD_CODE)) != 0) { continue; } if (ChunkNeedsReadWriteSection(chunk)) { continue; } offset = offset.AlignUp((int)chunk.alignment); chunk.block = roDataBlock + offset; chunk.blockRW = chunk.block; offset += (int)chunk.size; } Debug.Assert(offset <= roDataSize); } if (rwDataSize != 0) { _rwData = new byte[rwDataSize]; _rwDataBlob = new MethodReadWriteDataNode(MethodBeingCompiled); byte* rwDataBlock = (byte*)GetPin(_rwData); int offset = 0; foreach (ref AllocMemChunk chunk in chunks) { if ((chunk.flags & (CorJitAllocMemFlag.CORJIT_ALLOCMEM_HOT_CODE | CorJitAllocMemFlag.CORJIT_ALLOCMEM_COLD_CODE)) != 0) { continue; } if (ChunkNeedsReadWriteSection(chunk)) { offset = offset.AlignUp((int)chunk.alignment); chunk.block = rwDataBlock + offset; chunk.blockRW = chunk.block; offset += (int)chunk.size; } } Debug.Assert(offset <= rwDataSize); } if (_numFrameInfos > 0) { _frameInfos = new FrameInfo[_numFrameInfos]; } #if READYTORUN if (_numColdFrameInfos > 0) { _coldFrameInfos = new FrameInfo[_numColdFrameInfos]; } #endif } private void reserveUnwindInfo(bool isFunclet, bool isColdCode, uint unwindSize) { #if READYTORUN if (isColdCode) { _numColdFrameInfos++; } else #endif { _numFrameInfos++; } } private void allocUnwindInfo(byte* pHotCode, byte* pColdCode, uint startOffset, uint endOffset, uint unwindSize, byte* pUnwindBlock, CorJitFuncKind funcKind) { Debug.Assert(FrameInfoFlags.Filter == (FrameInfoFlags)CorJitFuncKind.CORJIT_FUNC_FILTER); Debug.Assert(FrameInfoFlags.Handler == (FrameInfoFlags)CorJitFuncKind.CORJIT_FUNC_HANDLER); FrameInfoFlags flags = (FrameInfoFlags)funcKind; if (funcKind == CorJitFuncKind.CORJIT_FUNC_ROOT) { if (this.MethodBeingCompiled.IsUnmanagedCallersOnly) flags |= FrameInfoFlags.ReversePInvoke; } byte[] blobData = null; if (pUnwindBlock != null || pColdCode == null) { blobData = new byte[unwindSize]; for (uint i = 0; i < unwindSize; i++) { blobData[i] = pUnwindBlock[i]; } } #if !READYTORUN var target = _compilation.TypeSystemContext.Target; if (target.Architecture == TargetArchitecture.ARM64 && target.OperatingSystem == TargetOS.Linux) { blobData = CompressARM64CFI(blobData); } #endif #if READYTORUN if (pColdCode == null) #endif { _frameInfos[_usedFrameInfos++] = new FrameInfo(flags, (int)startOffset, (int)endOffset, blobData); } #if READYTORUN else { _coldFrameInfos[_usedColdFrameInfos++] = new FrameInfo(flags, (int)startOffset, (int)endOffset, blobData); } #endif } private void* allocGCInfo(nuint size) { _gcInfo = new byte[size]; return (void*)GetPin(_gcInfo); } #pragma warning disable CA1822 // Mark members as static private bool logMsg(uint level, byte* fmt, IntPtr args) #pragma warning restore CA1822 // Mark members as static { // Console.WriteLine(Marshal.PtrToStringUTF8((IntPtr)fmt)); return false; } private int doAssert(byte* szFile, int iLine, byte* szExpr) { Logger.LogMessage(Marshal.PtrToStringUTF8((IntPtr)szFile) + ":" + iLine); Logger.LogMessage(Marshal.PtrToStringUTF8((IntPtr)szExpr)); return 1; } #pragma warning disable CA1822 // Mark members as static private void reportFatalError(CorJitResult result) #pragma warning restore CA1822 // Mark members as static { // We could add some logging here, but for now it's unnecessary. // CompileMethod is going to fail with this CorJitResult anyway. } private ArrayBuilder<Relocation> _codeRelocs; private ArrayBuilder<Relocation> _roDataRelocs; private ArrayBuilder<Relocation> _rwDataRelocs; #if READYTORUN private ArrayBuilder<Relocation> _coldCodeRelocs; #endif /// <summary> /// Various type of block. /// </summary> public enum BlockType : sbyte { /// <summary>Not a generated block.</summary> Unknown = -1, /// <summary>Represent code.</summary> Code = 0, /// <summary>Represent cold code (i.e. code not called frequently).</summary> ColdCode = 1, /// <summary>Read-only data.</summary> ROData = 2, /// <summary>Read-write data.</summary> RWData = 3, /// <summary>Instrumented Block Count Data</summary> BBCounts = 4 } private BlockType findKnownBlock(void* location, out int offset) { fixed (byte* pCode = _code) { if (pCode <= (byte*)location && (byte*)location < pCode + _code.Length) { offset = (int)((byte*)location - pCode); return BlockType.Code; } } if (_coldCode != null) { fixed (byte* pColdCode = _coldCode) { if (pColdCode <= (byte*)location && (byte*)location < pColdCode + _coldCode.Length) { offset = (int)((byte*)location - pColdCode); return BlockType.ColdCode; } } } if (_roData != null) { fixed (byte* pROData = _roData) { if (pROData <= (byte*)location && (byte*)location < pROData + _roData.Length) { offset = (int)((byte*)location - pROData); return BlockType.ROData; } } } if (_rwData != null) { fixed (byte* pRWData = _rwData) { if (pRWData <= (byte*)location && (byte*)location < pRWData + _rwData.Length) { offset = (int)((byte*)location - pRWData); return BlockType.RWData; } } } { BlockType retBlockType = BlockType.Unknown; offset = 0; findKnownBBCountBlock(ref retBlockType, location, ref offset); if (retBlockType == BlockType.BBCounts) return retBlockType; } offset = 0; return BlockType.Unknown; } partial void findKnownBBCountBlock(ref BlockType blockType, void* location, ref int offset); private ref ArrayBuilder<Relocation> findRelocBlock(BlockType blockType, out int length) { switch (blockType) { case BlockType.Code: length = _code.Length; return ref _codeRelocs; case BlockType.ROData: length = _roData.Length; return ref _roDataRelocs; case BlockType.RWData: length = _rwData.Length; return ref _rwDataRelocs; #if READYTORUN case BlockType.ColdCode: length = _coldCode.Length; return ref _coldCodeRelocs; #endif default: throw new NotImplementedException("Arbitrary relocs"); } } // Translates relocation type constants used by JIT to RelocType enumeration private RelocType GetRelocType(CorInfoReloc reloc) => reloc switch { CorInfoReloc.DIRECT => PointerSize == 8 ? RelocType.IMAGE_REL_BASED_DIR64 : RelocType.IMAGE_REL_BASED_HIGHLOW, CorInfoReloc.RELATIVE32 => RelocType.IMAGE_REL_BASED_REL32, CorInfoReloc.ARM64_BRANCH26 => RelocType.IMAGE_REL_BASED_ARM64_BRANCH26, CorInfoReloc.ARM64_PAGEBASE_REL21 => RelocType.IMAGE_REL_BASED_ARM64_PAGEBASE_REL21, CorInfoReloc.ARM64_PAGEOFFSET_12A => RelocType.IMAGE_REL_BASED_ARM64_PAGEOFFSET_12A, CorInfoReloc.ARM64_LIN_TLSDESC_ADR_PAGE21 => RelocType.IMAGE_REL_AARCH64_TLSDESC_ADR_PAGE21, CorInfoReloc.ARM64_LIN_TLSDESC_LD64_LO12 => RelocType.IMAGE_REL_AARCH64_TLSDESC_LD64_LO12, CorInfoReloc.ARM64_LIN_TLSDESC_ADD_LO12 => RelocType.IMAGE_REL_AARCH64_TLSDESC_ADD_LO12, CorInfoReloc.ARM64_LIN_TLSDESC_CALL => RelocType.IMAGE_REL_AARCH64_TLSDESC_CALL, CorInfoReloc.ARM64_WIN_TLS_SECREL_HIGH12A => RelocType.IMAGE_REL_ARM64_TLS_SECREL_HIGH12A, CorInfoReloc.ARM64_WIN_TLS_SECREL_LOW12A => RelocType.IMAGE_REL_ARM64_TLS_SECREL_LOW12A, CorInfoReloc.AMD64_WIN_SECREL => RelocType.IMAGE_REL_SECREL, CorInfoReloc.AMD64_LIN_TLSGD => RelocType.IMAGE_REL_TLSGD, CorInfoReloc.ARM32_THUMB_BRANCH24 => RelocType.IMAGE_REL_BASED_THUMB_BRANCH24, CorInfoReloc.ARM32_THUMB_MOV32 => RelocType.IMAGE_REL_BASED_THUMB_MOV32, CorInfoReloc.ARM32_THUMB_MOV32_PCREL => RelocType.IMAGE_REL_BASED_THUMB_MOV32_PCREL, CorInfoReloc.LOONGARCH64_PC => RelocType.IMAGE_REL_BASED_LOONGARCH64_PC, CorInfoReloc.LOONGARCH64_JIR => RelocType.IMAGE_REL_BASED_LOONGARCH64_JIR, CorInfoReloc.RISCV64_CALL_PLT => RelocType.IMAGE_REL_BASED_RISCV64_CALL_PLT, CorInfoReloc.RISCV64_PCREL_I => RelocType.IMAGE_REL_BASED_RISCV64_PCREL_I, CorInfoReloc.RISCV64_PCREL_S => RelocType.IMAGE_REL_BASED_RISCV64_PCREL_S, CorInfoReloc.WASM_FUNCTION_INDEX_LEB => RelocType.WASM_FUNCTION_INDEX_LEB, CorInfoReloc.WASM_TABLE_INDEX_SLEB => RelocType.WASM_TABLE_INDEX_SLEB, CorInfoReloc.WASM_MEMORY_ADDR_LEB => RelocType.WASM_MEMORY_ADDR_LEB, CorInfoReloc.WASM_MEMORY_ADDR_SLEB => RelocType.WASM_MEMORY_ADDR_SLEB, CorInfoReloc.WASM_MEMORY_ADDR_REL_SLEB => RelocType.WASM_MEMORY_ADDR_REL_SLEB, CorInfoReloc.WASM_MEMORY_ADDR_REL_LEB => RelocType.WASM_MEMORY_ADDR_REL_LEB, CorInfoReloc.WASM_TYPE_INDEX_LEB => RelocType.WASM_TYPE_INDEX_LEB, CorInfoReloc.WASM_GLOBAL_INDEX_LEB => RelocType.WASM_GLOBAL_INDEX_LEB, _ => throw new ArgumentException("Unsupported relocation type: " + reloc), }; private void recordRelocation(void* location, void* locationRW, void* target, CorInfoReloc fRelocType, int addlDelta) { int relocOffset; BlockType locationBlock = findKnownBlock(location, out relocOffset); Debug.Assert(locationBlock != BlockType.Unknown, "BlockType.Unknown not expected"); int length; ref ArrayBuilder<Relocation> sourceBlock = ref findRelocBlock(locationBlock, out length); int relocDelta; BlockType targetBlock = findKnownBlock(target, out relocDelta); ISymbolNode relocTarget; switch (targetBlock) { case BlockType.Code: relocTarget = _methodCodeNode; break; case BlockType.ColdCode: #if READYTORUN Debug.Assert(_methodColdCodeNode != null); relocTarget = _methodColdCodeNode; break; #else throw new NotImplementedException("ColdCode relocs"); #endif case BlockType.ROData: relocTarget = _roDataBlob; break; case BlockType.RWData: relocTarget = _rwDataBlob; break; #if READYTORUN case BlockType.BBCounts: relocTarget = null; break; #endif default: // Reloc points to something outside of the generated blocks var targetObject = HandleToObject(target); #if READYTORUN if (targetObject is RequiresRuntimeJitIfUsedSymbol requiresRuntimeSymbol) { throw new RequiresRuntimeJitException(requiresRuntimeSymbol.Message); } #endif relocTarget = (ISymbolNode)targetObject; break; } relocDelta += addlDelta; RelocType relocType = GetRelocType(fRelocType); // relocDelta is stored as the value Relocation.WriteValue(relocType, location, relocDelta); if (sourceBlock.Count == 0) sourceBlock.EnsureCapacity(length / 32 + 1); sourceBlock.Add(new Relocation(relocType, relocOffset, relocTarget)); } private CorInfoReloc getRelocTypeHint(void* target) { switch (_compilation.TypeSystemContext.Target.Architecture) { case TargetArchitecture.X64: return CorInfoReloc.RELATIVE32; #if READYTORUN case TargetArchitecture.ARM: return CorInfoReloc.ARM32_THUMB_BRANCH24; #endif default: return CorInfoReloc.NONE; } } private uint getExpectedTargetArchitecture() { TargetArchitecture arch = _compilation.TypeSystemContext.Target.Architecture; switch (arch) { case TargetArchitecture.X86: return (uint)CorInfoArch.CORINFO_ARCH_X86; case TargetArchitecture.X64: return (uint)CorInfoArch.CORINFO_ARCH_X64; case TargetArchitecture.ARM: return (uint)CorInfoArch.CORINFO_ARCH_ARM; case TargetArchitecture.ARM64: return (uint)CorInfoArch.CORINFO_ARCH_ARM64; case TargetArchitecture.LoongArch64: return (uint)CorInfoArch.CORINFO_ARCH_LOONGARCH64; case TargetArchitecture.RiscV64: return (uint)CorInfoArch.CORINFO_ARCH_RISCV64; case TargetArchitecture.Wasm32: return (uint)CorInfoArch.CORINFO_ARCH_WASM32; default: throw new NotImplementedException("Expected target architecture is not supported"); } } private bool doesFieldBelongToClass(CORINFO_FIELD_STRUCT_* fld, CORINFO_CLASS_STRUCT_* cls) { var field = HandleToObject(fld); var queryType = HandleToObject(cls); Debug.Assert(!field.IsStatic); // doesFieldBelongToClass implements the predicate of... // if field is not associated with the class in any way, return false. // if field is the only FieldDesc that the JIT might see for a given class handle // and logical field pair then return true. This is needed as the field handle here // is used as a key into a hashtable mapping writes to fields to value numbers. // // In this implementation this is made more complex as the JIT is exposed to CORINFO_FIELD_STRUCT // pointers which represent exact instantions, so performing exact matching is the necessary approach // BaseType._field, BaseType -> true // BaseType._field, DerivedType -> true // BaseType<__Canon>._field, BaseType<__Canon> -> true // BaseType<__Canon>._field, BaseType<string> -> false // BaseType<__Canon>._field, BaseType<object> -> false // BaseType<sbyte>._field, BaseType<sbyte> -> true // BaseType<sbyte>._field, BaseType<byte> -> false var fieldOwnerType = field.OwningType; while (queryType != null) { if (fieldOwnerType == queryType) return true; queryType = queryType.BaseType; } return false; } private bool isMethodDefinedInCoreLib() { TypeDesc owningType = MethodBeingCompiled.OwningType; MetadataType owningMetadataType = owningType as MetadataType; if (owningMetadataType == null) { return false; } return owningMetadataType.Module == _compilation.TypeSystemContext.SystemModule; } private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes) { // Read the user-defined configuration options. foreach (var flag in JitConfigProvider.Instance.Flags) flags.Set(flag); flags.InstructionSetFlags.Add(_compilation.InstructionSetSupport.OptimisticFlags); // Set the rest of the flags that don't make sense to expose publicly. flags.Set(CorJitFlag.CORJIT_FLAG_AOT); flags.Set(CorJitFlag.CORJIT_FLAG_RELOC); flags.Set(CorJitFlag.CORJIT_FLAG_USE_PINVOKE_HELPERS); TargetArchitecture targetArchitecture = _compilation.TypeSystemContext.Target.Architecture; switch (targetArchitecture) { case TargetArchitecture.X64: case TargetArchitecture.X86: Debug.Assert(InstructionSet.X86_X86Base == InstructionSet.X64_X86Base); Debug.Assert(_compilation.InstructionSetSupport.IsInstructionSetSupported(InstructionSet.X86_X86Base)); break; case TargetArchitecture.ARM64: Debug.Assert(_compilation.InstructionSetSupport.IsInstructionSetSupported(InstructionSet.ARM64_AdvSimd)); break; } if (targetArchitecture == TargetArchitecture.ARM && !_compilation.TypeSystemContext.Target.IsWindows) flags.Set(CorJitFlag.CORJIT_FLAG_RELATIVE_CODE_RELOCS); if (targetArchitecture == TargetArchitecture.RiscV64) flags.Set(CorJitFlag.CORJIT_FLAG_FRAMED); if (this.MethodBeingCompiled.IsUnmanagedCallersOnly) { // Validate UnmanagedCallersOnlyAttribute usage if (!this.MethodBeingCompiled.Signature.IsStatic) // Must be a static method { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramNonStaticMethod, this.MethodBeingCompiled); } if (this.MethodBeingCompiled.HasInstantiation || this.MethodBeingCompiled.OwningType.HasInstantiation) // No generics involved { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramGenericMethod, this.MethodBeingCompiled); } if (this.MethodBeingCompiled.IsAsync) { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramAsync, this.MethodBeingCompiled); } #if READYTORUN // TODO: enable this check in full AOT if (Marshaller.IsMarshallingRequired(this.MethodBeingCompiled.Signature, ((MetadataType)this.MethodBeingCompiled.OwningType).Module, this.MethodBeingCompiled.GetUnmanagedCallersOnlyMethodCallingConventions())) // Only blittable arguments { ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramNonBlittableTypes, this.MethodBeingCompiled); } #endif flags.Set(CorJitFlag.CORJIT_FLAG_REVERSE_PINVOKE); } if (this.MethodBeingCompiled.IsPInvoke) { flags.Set(CorJitFlag.CORJIT_FLAG_IL_STUB); } if (this.MethodBeingCompiled.IsNoOptimization) { flags.Set(CorJitFlag.CORJIT_FLAG_MIN_OPT); } if (this.MethodBeingCompiled.Context.Target.Abi == TargetAbi.NativeAotArmel) { flags.Set(CorJitFlag.CORJIT_FLAG_SOFTFP_ABI); } if (this.MethodBeingCompiled.IsAsyncCall() #if !READYTORUN || (_compilation.TypeSystemContext.IsSpecialUnboxingThunk(this.MethodBeingCompiled) && _compilation.TypeSystemContext.GetTargetOfSpecialUnboxingThunk(this.MethodBeingCompiled).IsAsyncCall()) || (_compilation.TypeSystemContext.IsDefaultInterfaceMethodImplementationInstantiationThunk(this.MethodBeingCompiled) && _compilation.TypeSystemContext.GetTargetOfDefaultInterfaceMethodImplementationInstantiationThunk(this.MethodBeingCompiled).IsAsyncCall()) #endif ) { flags.Set(CorJitFlag.CORJIT_FLAG_ASYNC); // Runtime cannot handle hot/cold splitting for async methods flags.Clear(CorJitFlag.CORJIT_FLAG_PROCSPLIT); } #if READYTORUN if (this.MethodBeingCompiled.Context.Target.OperatingSystem == TargetOS.Browser) { flags.Set(CorJitFlag.CORJIT_FLAG_PORTABLE_ENTRY_POINTS); } #endif return (uint)sizeof(CORJIT_FLAGS); } private MemoryStream _cachedMemoryStream = new MemoryStream(); public static void ComputeJitPgoInstrumentationSchema(Func<object, IntPtr> objectToHandle, PgoSchemaElem[] pgoResultsSchemas, out PgoInstrumentationSchema[] nativeSchemas, MemoryStream instrumentationData, Func<TypeDesc, bool> typeFilter = null) { nativeSchemas = new PgoInstrumentationSchema[pgoResultsSchemas.Length]; instrumentationData.SetLength(0); BinaryWriter bwInstrumentationData = new BinaryWriter(instrumentationData); for (int i = 0; i < nativeSchemas.Length; i++) { if ((bwInstrumentationData.BaseStream.Position % 8) == 4) { bwInstrumentationData.Write(0); } Debug.Assert((bwInstrumentationData.BaseStream.Position % 8) == 0); nativeSchemas[i].Offset = new IntPtr(checked((int)bwInstrumentationData.BaseStream.Position)); nativeSchemas[i].ILOffset = pgoResultsSchemas[i].ILOffset; nativeSchemas[i].Count = pgoResultsSchemas[i].Count; nativeSchemas[i].Other = pgoResultsSchemas[i].Other; nativeSchemas[i].InstrumentationKind = (PgoInstrumentationKind)pgoResultsSchemas[i].InstrumentationKind; if (pgoResultsSchemas[i].DataObject == null) { bwInstrumentationData.Write(pgoResultsSchemas[i].DataLong); } else { object dataObject = pgoResultsSchemas[i].DataObject; if (dataObject is int[] intArray) { foreach (int intVal in intArray) bwInstrumentationData.Write(intVal); } else if (dataObject is long[] longArray) { foreach (long longVal in longArray) bwInstrumentationData.Write(longVal); } else if (dataObject is TypeSystemEntityOrUnknown[] typeArray) { foreach (TypeSystemEntityOrUnknown typeVal in typeArray) { nint ptrVal; if (typeVal.AsType != null && (typeFilter == null || typeFilter(typeVal.AsType))) { ptrVal = (IntPtr)objectToHandle(typeVal.AsType); } else if (typeVal.AsMethod != null) { ptrVal = (IntPtr)objectToHandle(typeVal.AsMethod); } else { // The "Unknown types are the values from 1-33 ptrVal = new IntPtr((typeVal.AsUnknown % 32) + 1); } if (IntPtr.Size == 4) bwInstrumentationData.Write((int)ptrVal); else bwInstrumentationData.Write((long)ptrVal); } } } } bwInstrumentationData.Flush(); } private HRESULT getPgoInstrumentationResults(CORINFO_METHOD_STRUCT_* ftnHnd, ref PgoInstrumentationSchema* pSchema, ref uint countSchemaItems, byte** pInstrumentationData, ref PgoSource pPgoSource, ref bool pDynamicPgo) { MethodDesc methodDesc = HandleToObject(ftnHnd); if (!_pgoResults.TryGetValue(methodDesc, out PgoInstrumentationResults pgoResults)) { #if READYTORUN PgoSchemaElem[] pgoResultsSchemas = _compilation.ProfileData.GetAllowSynthesis(_compilation, methodDesc, out bool isSynthesized)?.SchemaData; if (pgoResultsSchemas != null && isSynthesized && _compilation.ProfileData.EmbedPgoDataInR2RImage) { if (_synthesizedPgoDependencies == null) _synthesizedPgoDependencies = new HashSet<MethodDesc>(); _synthesizedPgoDependencies.Add(methodDesc); } #else PgoSchemaElem[] pgoResultsSchemas = _compilation.ProfileData[methodDesc]?.SchemaData; #endif if (pgoResultsSchemas == null) { pgoResults.hr = HRESULT.E_NOTIMPL; } else { #pragma warning disable SA1001, SA1113, SA1115 // Commas should be spaced correctly ComputeJitPgoInstrumentationSchema(ObjectToHandle, pgoResultsSchemas, out var nativeSchemas, _cachedMemoryStream #if !READYTORUN , _compilation.CanReferenceConstructedMethodTable #endif ); #pragma warning restore SA1001, SA1113, SA1115 // Commas should be spaced correctly var instrumentationData = _cachedMemoryStream.ToArray(); pgoResults.pInstrumentationData = (byte*)GetPin(instrumentationData); pgoResults.countSchemaItems = (uint)nativeSchemas.Length; pgoResults.pSchema = (PgoInstrumentationSchema*)GetPin(nativeSchemas); pgoResults.hr = HRESULT.S_OK; } _pgoResults.Add(methodDesc, pgoResults); } pSchema = pgoResults.pSchema; countSchemaItems = pgoResults.countSchemaItems; *pInstrumentationData = pgoResults.pInstrumentationData; pPgoSource = PgoSource.Static; pDynamicPgo = false; return pgoResults.hr; } #if READYTORUN InstructionSetFlags _actualInstructionSetSupported; InstructionSetFlags _actualInstructionSetUnsupported; private bool notifyInstructionSetUsage(InstructionSet instructionSet, bool supportEnabled) { instructionSet = InstructionSetFlags.ConvertToImpliedInstructionSetForVectorInstructionSets(_compilation.TypeSystemContext.Target.Architecture, instructionSet); Debug.Assert(!_compilation.InstructionSetSupport.NonSpecifiableFlags.HasInstructionSet(instructionSet)); if (supportEnabled) { _actualInstructionSetSupported.AddInstructionSet(instructionSet); } else { // By policy we code review all changes into corelib, such that failing to use an instruction // set is not a reason to not support usage of it. Except for functions which check if a given // feature is supported or hardware accelerated. if (!isMethodDefinedInCoreLib()) { _actualInstructionSetUnsupported.AddInstructionSet(instructionSet); } else { // We want explicitly implemented ISimdVector<TSelf, T> APIs to still be expanded where possible // but, they all prefix the qualified name of the interface first, so we'll check for that and // skip the prefix before trying to resolve the method. ReadOnlySpan<char> methodName = MethodBeingCompiled.GetName().AsSpan(); if (methodName.StartsWith("System.Runtime.Intrinsics.ISimdVector<System.")) { ReadOnlySpan<char> partialMethodName = methodName.Slice(45); if (partialMethodName.StartsWith("Numerics.Vector")) { partialMethodName = partialMethodName.Slice(15); if (partialMethodName.StartsWith("<T>,T>.")) { methodName = partialMethodName.Slice(7); } } if (partialMethodName.StartsWith("Runtime.Intrinsics.Vector")) { partialMethodName = partialMethodName.Slice(25); if (partialMethodName.StartsWith("64<T>,T>.")) { methodName = partialMethodName.Slice(9); } else if (partialMethodName.StartsWith("128<T>,T>.") || partialMethodName.StartsWith("256<T>,T>.") || partialMethodName.StartsWith("512<T>,T>.")) { methodName = partialMethodName.Slice(10); } } } if (methodName.Equals("get_IsSupported", StringComparison.Ordinal) || methodName.Equals("get_IsHardwareAccelerated", StringComparison.Ordinal)) { _actualInstructionSetUnsupported.AddInstructionSet(instructionSet); } } } return supportEnabled; } #else private bool notifyInstructionSetUsage(InstructionSet instructionSet, bool supportEnabled) { instructionSet = InstructionSetFlags.ConvertToImpliedInstructionSetForVectorInstructionSets(_compilation.TypeSystemContext.Target.Architecture, instructionSet); Debug.Assert(!_compilation.InstructionSetSupport.NonSpecifiableFlags.HasInstructionSet(instructionSet)); return supportEnabled ? _compilation.InstructionSetSupport.IsInstructionSetSupported(instructionSet) : false; } #endif private static bool TryReadRvaFieldData(FieldDesc field, byte* buffer, int bufferSize, int valueOffset) { Debug.Assert(buffer != null); Debug.Assert(bufferSize > 0); Debug.Assert(valueOffset >= 0); Debug.Assert(field.IsStatic); Debug.Assert(field.HasRva); if (!field.IsThreadStatic && field.IsInitOnly && field is EcmaField ecmaField) { ReadOnlySpan<byte> rvaData = ecmaField.GetFieldRvaData(); if (rvaData.Length >= bufferSize && valueOffset <= rvaData.Length - bufferSize) { rvaData.Slice(valueOffset, bufferSize).CopyTo(new Span<byte>(buffer, bufferSize)); return true; } } return false; } private CORINFO_METHOD_STRUCT_* getSpecialCopyHelper(CORINFO_CLASS_STRUCT_* type) { throw new NotImplementedException("getSpecialCopyHelper"); } } } |