// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // This is where we group together all the runtime export calls. // using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Internal.Runtime; namespace System.Runtime { internal static partial class RuntimeExports { // // internal calls for allocation // [RuntimeExport("RhNewObject")] public static unsafe object RhNewObject(MethodTable* pEEType) { // This is structured in a funny way because at the present state of things, the Debug.Assert // below will call into the assert defined in the class library (and not the MRT version of it). The one // in the class library is not low level enough to be callable when GC statics are not initialized yet. // Feel free to restructure once that's not a problem. #if DEBUG bool isValid = !pEEType->IsGenericTypeDefinition && !pEEType->IsInterface && !pEEType->IsArray && !pEEType->IsString && !pEEType->IsPointer && !pEEType->IsFunctionPointer && !pEEType->IsByRefLike; if (!isValid) Debug.Assert(false); #endif #if FEATURE_64BIT_ALIGNMENT if (pEEType->RequiresAlign8) { if (pEEType->IsValueType) return InternalCalls.RhpNewFastMisalign(pEEType); if (pEEType->IsFinalizable) return InternalCalls.RhpNewFinalizableAlign8(pEEType); return InternalCalls.RhpNewFastAlign8(pEEType); } else #endif // FEATURE_64BIT_ALIGNMENT { if (pEEType->IsFinalizable) return InternalCalls.RhpNewFinalizable(pEEType); return InternalCalls.RhpNewFast(pEEType); } } [RuntimeExport("RhNewArray")] public static unsafe object RhNewArray(MethodTable* pEEType, nint length) { Debug.Assert(pEEType->IsSzArray); #if FEATURE_64BIT_ALIGNMENT MethodTable* pEEElementType = pEEType->RelatedParameterType; if (pEEElementType->IsValueType && pEEElementType->RequiresAlign8) { return InternalCalls.RhpNewArrayFastAlign8(pEEType, length); } else #endif // FEATURE_64BIT_ALIGNMENT { return InternalCalls.RhpNewArrayFast(pEEType, length); } } [RuntimeExport("RhNewVariableSizeObject")] public static unsafe object RhNewVariableSizeObject(MethodTable* pEEType, int length) { Debug.Assert(pEEType->IsArray || pEEType->IsString); object array; #if FEATURE_64BIT_ALIGNMENT MethodTable* pEEElementType = pEEType->RelatedParameterType; if (pEEElementType->IsValueType && pEEElementType->RequiresAlign8) { RuntimeImports.RhAllocateNewArray(pEEType, (uint)length, (uint)GC_ALLOC_FLAGS.GC_ALLOC_ALIGN8, &array); } else #endif // FEATURE_64BIT_ALIGNMENT { RuntimeImports.RhAllocateNewArray(pEEType, (uint)length, (uint)GC_ALLOC_FLAGS.GC_ALLOC_NO_FLAGS, &array); } if (array == null) throw new OutOfMemoryException(); return array; } [RuntimeExport("RhGetNewObjectHelper")] internal static unsafe IntPtr RhGetNewObjectHelper(MethodTable* pEEType) { #if FEATURE_64BIT_ALIGNMENT if (pEEType->RequiresAlign8) { if (pEEType->IsFinalizable) return (IntPtr)(delegate*<MethodTable*, object>)&InternalCalls.RhpNewFinalizableAlign8; else if (pEEType->IsValueType) // returns true for enum types as well return (IntPtr)(delegate*<MethodTable*, object>)&InternalCalls.RhpNewFastMisalign; else return (IntPtr)(delegate*<MethodTable*, object>)&InternalCalls.RhpNewFastAlign8; } #endif // FEATURE_64BIT_ALIGNMENT if (pEEType->IsFinalizable) return (IntPtr)(delegate*<MethodTable*, object>)&InternalCalls.RhpNewFinalizable; else return (IntPtr)(delegate*<MethodTable*, object>)&InternalCalls.RhpNewFast; } public static unsafe object RhBox(MethodTable* pEEType, ref byte data) { // A null can be passed for boxing of a null ref. _ = Unsafe.ReadUnaligned<byte>(ref data); ref byte dataAdjustedForNullable = ref data; // Can box non-ByRefLike value types only (which also implies no finalizers). Debug.Assert(pEEType->IsValueType && !pEEType->IsByRefLike && !pEEType->IsFinalizable); // If we're boxing a Nullable<T> then either box the underlying T or return null (if the // nullable's value is empty). if (pEEType->IsNullable) { // The boolean which indicates whether the value is null comes first in the Nullable struct. if (data == 0) return null; // Switch type we're going to box to the Nullable<T> target type and advance the data pointer // to the value embedded within the nullable. dataAdjustedForNullable = ref Unsafe.Add(ref data, pEEType->NullableValueOffset); pEEType = pEEType->NullableType; } object result; #if FEATURE_64BIT_ALIGNMENT if (pEEType->RequiresAlign8) { result = InternalCalls.RhpNewFastMisalign(pEEType); } else #endif // FEATURE_64BIT_ALIGNMENT { result = InternalCalls.RhpNewFast(pEEType); } // Copy the unboxed value type data into the new object. // Perform any write barriers necessary for embedded reference fields. if (pEEType->ContainsGCPointers) { InternalCalls.RhBulkMoveWithWriteBarrier(ref result.GetRawData(), ref dataAdjustedForNullable, pEEType->ValueTypeSize); } else { Unsafe.CopyBlock(ref result.GetRawData(), ref dataAdjustedForNullable, pEEType->ValueTypeSize); } return result; } [RuntimeExport("RhBoxAny")] public static unsafe object RhBoxAny(ref byte data, MethodTable* pEEType) { if (pEEType->IsValueType) { return RhBox(pEEType, ref data); } else { return Unsafe.As<byte, object>(ref data); } } private static unsafe bool UnboxAnyTypeCompare(MethodTable* pEEType, MethodTable* ptrUnboxToEEType) { if (pEEType == ptrUnboxToEEType) return true; if (pEEType->ElementType == ptrUnboxToEEType->ElementType) { // Enum's and primitive types should pass the UnboxAny exception cases // if they have an exactly matching cor element type. switch (ptrUnboxToEEType->ElementType) { case EETypeElementType.Byte: case EETypeElementType.SByte: case EETypeElementType.Int16: case EETypeElementType.UInt16: case EETypeElementType.Int32: case EETypeElementType.UInt32: case EETypeElementType.Int64: case EETypeElementType.UInt64: case EETypeElementType.IntPtr: case EETypeElementType.UIntPtr: return true; } } return false; } // // Unbox helpers with RyuJIT conventions // public static unsafe ref byte RhUnbox2(MethodTable* pUnboxToEEType, object obj) { if ((obj == null) || !UnboxAnyTypeCompare(obj.GetMethodTable(), pUnboxToEEType)) { ExceptionIDs exID = obj == null ? ExceptionIDs.NullReference : ExceptionIDs.InvalidCast; throw pUnboxToEEType->GetClasslibException(exID); } return ref obj.GetRawData(); } public static unsafe void RhUnboxNullable(ref byte data, MethodTable* pUnboxToEEType, object obj) { if (obj != null && obj.GetMethodTable() != pUnboxToEEType->NullableType) { throw pUnboxToEEType->GetClasslibException(ExceptionIDs.InvalidCast); } RhUnbox(obj, ref data, pUnboxToEEType); } public static unsafe void RhUnboxTypeTest(MethodTable* pType, MethodTable* pBoxType) { Debug.Assert(pType->IsValueType); if (!UnboxAnyTypeCompare(pType, pBoxType)) { throw pType->GetClasslibException(ExceptionIDs.InvalidCast); } } [RuntimeExport("RhUnbox")] public static unsafe void RhUnbox(object? obj, ref byte data, MethodTable* pUnboxToEEType) { // When unboxing to a Nullable the input object may be null. if (obj == null) { Debug.Assert(pUnboxToEEType != null && pUnboxToEEType->IsNullable); // Set HasValue to false and clear the value (in case there were GC references we wish to stop reporting). InternalCalls.RhpGcSafeZeroMemory( ref data, pUnboxToEEType->ValueTypeSize); return; } MethodTable* pEEType = obj.GetMethodTable(); // Can unbox value types only. Debug.Assert(pEEType->IsValueType); // A special case is that we can unbox a value type T into a Nullable<T>. It's the only case where // pUnboxToEEType is useful. Debug.Assert((pUnboxToEEType == null) || UnboxAnyTypeCompare(pEEType, pUnboxToEEType) || pUnboxToEEType->IsNullable); if (pUnboxToEEType != null && pUnboxToEEType->IsNullable) { Debug.Assert(pUnboxToEEType->NullableType == pEEType); // Set the first field of the Nullable to true to indicate the value is present. Unsafe.As<byte, bool>(ref data) = true; // Adjust the data pointer so that it points at the value field in the Nullable. data = ref Unsafe.Add(ref data, pUnboxToEEType->NullableValueOffset); } ref byte fields = ref obj.GetRawData(); if (pEEType->ContainsGCPointers) { // Copy the boxed fields into the new location in a GC safe manner InternalCalls.RhBulkMoveWithWriteBarrier(ref data, ref fields, pEEType->ValueTypeSize); } else { // Copy the boxed fields into the new location. Unsafe.CopyBlock(ref data, ref fields, pEEType->ValueTypeSize); } } [RuntimeExport("RhGetCurrentThreadStackTrace")] [MethodImpl(MethodImplOptions.NoInlining)] // Ensures that the RhGetCurrentThreadStackTrace frame is always present public static unsafe int RhGetCurrentThreadStackTrace(IntPtr[] outputBuffer) { fixed (IntPtr* pOutputBuffer = outputBuffer) return RhpGetCurrentThreadStackTrace(pOutputBuffer, (uint)((outputBuffer != null) ? outputBuffer.Length : 0), new UIntPtr(&pOutputBuffer)); } #pragma warning disable SYSLIB1054 // Use DllImport here instead of LibraryImport because this file is used by Test.CoreLib. [DllImport("*")] private static extern unsafe int RhpGetCurrentThreadStackTrace(IntPtr* pOutputBuffer, uint outputBufferLength, UIntPtr addressInCurrentFrame); #pragma warning restore SYSLIB1054 // Worker for RhGetCurrentThreadStackTrace. RhGetCurrentThreadStackTrace just allocates a transition // frame that will be used to seed the stack trace and this method does all the real work. // // Input: outputBuffer may be null or non-null // Return value: positive: number of entries written to outputBuffer // negative: number of required entries in outputBuffer in case it's too small (or null) // Output: outputBuffer is filled in with return address IPs, starting with placing the this // method's return address into index 0 // // NOTE: We don't want to allocate the array on behalf of the caller because we don't know which class // library's objects the caller understands (we support multiple class libraries with multiple root // System.Object types). [UnmanagedCallersOnly(EntryPoint = "RhpCalculateStackTraceWorker")] private static unsafe int RhpCalculateStackTraceWorker(IntPtr* pOutputBuffer, uint outputBufferLength, UIntPtr addressInCurrentFrame) { uint nFrames = 0; bool success = true; StackFrameIterator frameIter = default; bool isValid = frameIter.Init(null); Debug.Assert(isValid, "Missing RhGetCurrentThreadStackTrace frame"); // Note that the while loop will skip RhGetCurrentThreadStackTrace frame while (frameIter.Next()) { if ((void*)frameIter.SP < (void*)addressInCurrentFrame) continue; if (nFrames < outputBufferLength) pOutputBuffer[nFrames] = new IntPtr(frameIter.ControlPC); else success = false; nFrames++; } return success ? (int)nFrames : -(int)nFrames; } } }