Skip to content

Commit 5b7dd45

Browse files
authored
Port managed Array.Copy/Clear from CoreCLR version (dotnet#225)
Also fixes a few infrastructure issues that I have run into along the way.
1 parent 1b84856 commit 5b7dd45

File tree

7 files changed

+92
-146
lines changed

7 files changed

+92
-146
lines changed

docs/workflow/building/coreclr/nativeaot.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ The Native AOT toolchain can be currently built for Linux, macOS and Windows x64
88
- Run `build[.cmd|.sh] nativeaot+libs+installer -rc [Debug|Release] -lc Release`. This will restore nuget packages required for building and build the parts of the repo required for the Native AOT toolchain.
99
- The build will place the toolchain packages at `artifacts\packages\[Debug|Release]\Shipping`. To publish your project using these packages:
1010
- Add the package directory to your `nuget.config` file. For example, replace `dotnet-experimental` line in `samples\HelloWorld\nuget.config` with `<add key="local" value="C:\runtimelab\artifacts\packages\Debug\Shipping" />`
11-
- Run `dotnet restore --packages pkg -r [win-x64|linux-x64|osx-64]` to restore the package into your project. `--package pkg` option restores the package into a local directory that is easy to cleanup once you are done. It avoids polluting the global nuget cache with your locally built dev package.
12-
- Publish your project as usual: `dotnet publish -r [win-x64|linux-x64|osx-64] -c Release`.
11+
- Run `dotnet publish --packages pkg -r [win-x64|linux-x64|osx-64] -c [Debug|Release]` to publish your project. `--package pkg` option restores the package into a local directory that is easy to cleanup once you are done. It avoids polluting the global nuget cache with your locally built dev package.
1312

1413
## Visual Studio Solutions
1514

src/coreclr/src/dlls/mscoree/coreclr/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ set(CORECLR_LIBRARIES
109109
ildbsymlib
110110
utilcode
111111
v3binder
112-
System.Globalization.Native-static
112+
System.Globalization.Native-Static
113113
interop
114114
)
115115

src/coreclr/src/nativeaot/Runtime/MiscHelpers.cpp

-90
Original file line numberDiff line numberDiff line change
@@ -366,96 +366,6 @@ COOP_PINVOKE_HELPER(UInt8 *, RhGetCodeTarget, (UInt8 * pCodeOrg))
366366
return pCodeOrg;
367367
}
368368

369-
//
370-
// Return true if the array slice is valid
371-
//
372-
FORCEINLINE bool CheckArraySlice(Array * pArray, Int32 index, Int32 length)
373-
{
374-
Int32 arrayLength = pArray->GetArrayLength();
375-
376-
return (0 <= index) && (index <= arrayLength) &&
377-
(0 <= length) && (length <= arrayLength) &&
378-
(length <= arrayLength - index);
379-
}
380-
381-
//
382-
// This function handles all cases of Array.Copy that do not require conversions or casting. It returns false if the copy cannot be performed, leaving
383-
// the handling of the complex cases or throwing appropriate exception to the higher level framework.
384-
//
385-
COOP_PINVOKE_HELPER(Boolean, RhpArrayCopy, (Array * pSourceArray, Int32 sourceIndex, Array * pDestinationArray, Int32 destinationIndex, Int32 length))
386-
{
387-
if (pSourceArray == NULL || pDestinationArray == NULL)
388-
return false;
389-
390-
EEType* pArrayType = pSourceArray->get_EEType();
391-
EEType* pDestinationArrayType = pDestinationArray->get_EEType();
392-
if (pArrayType != pDestinationArrayType)
393-
{
394-
if (!pArrayType->IsEquivalentTo(pDestinationArrayType))
395-
return false;
396-
}
397-
398-
size_t componentSize = pArrayType->get_ComponentSize();
399-
if (componentSize == 0) // Not an array
400-
return false;
401-
402-
if (!CheckArraySlice(pSourceArray, sourceIndex, length))
403-
return false;
404-
405-
if (!CheckArraySlice(pDestinationArray, destinationIndex, length))
406-
return false;
407-
408-
if (length == 0)
409-
return true;
410-
411-
UInt8 * pSourceData = (UInt8 *)pSourceArray->GetArrayData() + sourceIndex * componentSize;
412-
UInt8 * pDestinationData = (UInt8 *)pDestinationArray->GetArrayData() + destinationIndex * componentSize;
413-
size_t size = length * componentSize;
414-
415-
if (pArrayType->HasReferenceFields())
416-
{
417-
if (pDestinationData <= pSourceData || pSourceData + size <= pDestinationData)
418-
InlineForwardGCSafeCopy(pDestinationData, pSourceData, size);
419-
else
420-
InlineBackwardGCSafeCopy(pDestinationData, pSourceData, size);
421-
422-
InlinedBulkWriteBarrier(pDestinationData, size);
423-
}
424-
else
425-
{
426-
memmove(pDestinationData, pSourceData, size);
427-
}
428-
429-
return true;
430-
}
431-
432-
//
433-
// This function handles all cases of Array.Clear that do not require conversions. It returns false if the operation cannot be performed, leaving
434-
// the handling of the complex cases or throwing appropriate exception to the higher level framework. It is only allowed to return false for illegal
435-
// calls as the BCL side has fallback for "complex cases" only.
436-
//
437-
COOP_PINVOKE_HELPER(Boolean, RhpArrayClear, (Array * pArray, Int32 index, Int32 length))
438-
{
439-
if (pArray == NULL)
440-
return false;
441-
442-
EEType* pArrayType = pArray->get_EEType();
443-
444-
size_t componentSize = pArrayType->get_ComponentSize();
445-
if (componentSize == 0) // Not an array
446-
return false;
447-
448-
if (!CheckArraySlice(pArray, index, length))
449-
return false;
450-
451-
if (length == 0)
452-
return true;
453-
454-
InlineGCSafeFillMemory((UInt8 *)pArray->GetArrayData() + index * componentSize, length * componentSize, 0);
455-
456-
return true;
457-
}
458-
459369
// Get the universal transition thunk. If the universal transition stub is called through
460370
// the normal PE static linkage model, a jump stub would be used which may interfere with
461371
// the custom calling convention of the universal transition thunk. So instead, a special

src/coreclr/src/nativeaot/System.Private.CoreLib/src/System/Array.CoreRT.cs

+87-29
Original file line numberDiff line numberDiff line change
@@ -227,30 +227,74 @@ private ref int GetRawMultiDimArrayBounds()
227227
return ref Unsafe.AddByteOffset(ref _numComponents, POINTER_SIZE);
228228
}
229229

230-
// Copies length elements from sourceArray, starting at sourceIndex, to
231-
// destinationArray, starting at destinationIndex.
232-
//
233-
public static void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
234-
{
235-
if (!RuntimeImports.TryArrayCopy(sourceArray, sourceIndex, destinationArray, destinationIndex, length))
236-
CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, false);
237-
}
238-
239230
// Provides a strong exception guarantee - either it succeeds, or
240231
// it throws an exception with no side effects. The arrays must be
241232
// compatible array types based on the array element type - this
242233
// method does not support casting, boxing, or primitive widening.
243234
// It will up-cast, assuming the array types are correct.
244235
public static void ConstrainedCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
245236
{
246-
if (!RuntimeImports.TryArrayCopy(sourceArray, sourceIndex, destinationArray, destinationIndex, length))
247-
CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, true);
237+
CopyImpl(sourceArray, sourceIndex, destinationArray, destinationIndex, length, reliable: true);
248238
}
249239

250240
public static void Copy(Array sourceArray, Array destinationArray, int length)
251241
{
252-
if (!RuntimeImports.TryArrayCopy(sourceArray, 0, destinationArray, 0, length))
253-
CopyImpl(sourceArray, 0, destinationArray, 0, length, false);
242+
if (sourceArray is null)
243+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
244+
if (destinationArray is null)
245+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);
246+
247+
EETypePtr eeType = sourceArray.EETypePtr;
248+
if (eeType.FastEquals(destinationArray.EETypePtr) &&
249+
eeType.IsSzArray &&
250+
(uint)length <= (nuint)sourceArray.LongLength &&
251+
(uint)length <= (nuint)destinationArray.LongLength)
252+
{
253+
nuint byteCount = (uint)length * (nuint)eeType.ComponentSize;
254+
ref byte src = ref Unsafe.As<RawArrayData>(sourceArray).Data;
255+
ref byte dst = ref Unsafe.As<RawArrayData>(destinationArray).Data;
256+
257+
if (eeType.HasPointers)
258+
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
259+
else
260+
Buffer.Memmove(ref dst, ref src, byteCount);
261+
262+
// GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
263+
return;
264+
}
265+
266+
// Less common
267+
CopyImpl(sourceArray, sourceArray.GetLowerBound(0), destinationArray, destinationArray.GetLowerBound(0), length, reliable: false);
268+
}
269+
270+
public static unsafe void Copy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
271+
{
272+
if (sourceArray != null && destinationArray != null)
273+
{
274+
EETypePtr eeType = sourceArray.EETypePtr;
275+
if (eeType.FastEquals(destinationArray.EETypePtr) &&
276+
eeType.IsSzArray &&
277+
length >= 0 && sourceIndex >= 0 && destinationIndex >= 0 &&
278+
(uint)(sourceIndex + length) <= (nuint)sourceArray.LongLength &&
279+
(uint)(destinationIndex + length) <= (nuint)destinationArray.LongLength)
280+
{
281+
nuint elementSize = (nuint)eeType.ComponentSize;
282+
nuint byteCount = (uint)length * elementSize;
283+
ref byte src = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(sourceArray).Data, (uint)sourceIndex * elementSize);
284+
ref byte dst = ref Unsafe.AddByteOffset(ref Unsafe.As<RawArrayData>(destinationArray).Data, (uint)destinationIndex * elementSize);
285+
286+
if (eeType.HasPointers)
287+
Buffer.BulkMoveWithWriteBarrier(ref dst, ref src, byteCount);
288+
else
289+
Buffer.Memmove(ref dst, ref src, byteCount);
290+
291+
// GC.KeepAlive(sourceArray) not required. pMT kept alive via sourceArray
292+
return;
293+
}
294+
}
295+
296+
// Less common
297+
CopyImpl(sourceArray!, sourceIndex, destinationArray!, destinationIndex, length, reliable: false);
254298
}
255299

256300
//
@@ -260,9 +304,9 @@ public static void Copy(Array sourceArray, Array destinationArray, int length)
260304
private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable)
261305
{
262306
if (sourceArray is null)
263-
throw new ArgumentNullException(nameof(sourceArray));
307+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceArray);
264308
if (destinationArray is null)
265-
throw new ArgumentNullException(nameof(destinationArray));
309+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.destinationArray);
266310

267311
int sourceRank = sourceArray.Rank;
268312
int destinationRank = destinationArray.Rank;
@@ -836,24 +880,38 @@ private static unsafe void CopyImplPrimitiveTypeWithWidening(Array sourceArray,
836880
}
837881
}
838882

839-
public static void Clear(Array array, int index, int length)
883+
public static unsafe void Clear(Array array, int index, int length)
840884
{
841-
if (!RuntimeImports.TryArrayClear(array, index, length))
842-
ReportClearErrors(array, index, length);
843-
}
885+
if (array == null)
886+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
844887

845-
private static unsafe void ReportClearErrors(Array array, int index, int length)
846-
{
847-
if (array is null)
848-
throw new ArgumentNullException(nameof(array));
888+
ref byte p = ref Unsafe.As<RawArrayData>(array).Data;
889+
int lowerBound = 0;
849890

850-
if (index < 0 || index > array.Length || length < 0 || length > array.Length)
851-
throw new IndexOutOfRangeException();
852-
if (length > (array.Length - index))
853-
throw new IndexOutOfRangeException();
891+
EETypePtr eeType = array.EETypePtr;
892+
if (!eeType.IsSzArray)
893+
{
894+
int rank = eeType.ArrayRank;
895+
lowerBound = Unsafe.Add(ref Unsafe.As<byte, int>(ref p), rank);
896+
p = ref Unsafe.Add(ref p, 2 * sizeof(int) * rank); // skip the bounds
897+
}
898+
899+
int offset = index - lowerBound;
900+
901+
if (index < lowerBound || offset < 0 || length < 0 || (uint)(offset + length) > (nuint)array.LongLength)
902+
ThrowHelper.ThrowIndexOutOfRangeException();
903+
904+
nuint elementSize = eeType.ComponentSize;
905+
906+
ref byte ptr = ref Unsafe.AddByteOffset(ref p, (uint)offset * elementSize);
907+
nuint byteLength = (uint)length * elementSize;
908+
909+
if (eeType.HasPointers)
910+
SpanHelpers.ClearWithReferences(ref Unsafe.As<byte, IntPtr>(ref ptr), byteLength / (uint)sizeof(IntPtr));
911+
else
912+
SpanHelpers.ClearWithoutReferences(ref ptr, byteLength);
854913

855-
// The above checks should have covered all the reasons why Clear would fail.
856-
Debug.Assert(false);
914+
// GC.KeepAlive(array) not required. pMT kept alive via `ptr`
857915
}
858916

859917
public int GetLength(int dimension)

src/coreclr/src/nativeaot/System.Private.CoreLib/src/System/Buffer.CoreRT.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ internal static unsafe void Memmove<T>(ref T destination, ref T source, nuint el
4242
{
4343
// Blittable memmove
4444

45-
RuntimeImports.memmove(
46-
(byte*)Unsafe.AsPointer(ref destination),
47-
(byte*)Unsafe.AsPointer(ref source),
45+
Memmove(
46+
ref Unsafe.As<T, byte>(ref destination),
47+
ref Unsafe.As<T, byte>(ref source),
4848
elementCount * (nuint)Unsafe.SizeOf<T>());
4949
}
5050
else

src/coreclr/src/nativeaot/System.Private.CoreLib/src/System/EETypePtr.cs

-13
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,6 @@ public bool FastEquals(EETypePtr other)
9393
return RuntimeImports.AreTypesEquivalent(this, other);
9494
}
9595

96-
//
97-
// An even faster version of FastEquals that only checks if two EEType pointers are identical.
98-
// Note: this method might return false for cases where FastEquals would return true.
99-
// Only use if you know what you're doing.
100-
//
101-
internal bool FastEqualsUnreliable(EETypePtr other)
102-
{
103-
Debug.Assert(!this.IsNull);
104-
Debug.Assert(!other.IsNull);
105-
106-
return this.RawValue == other.RawValue;
107-
}
108-
10996
// Caution: You cannot safely compare RawValue's as RH does NOT unify EETypes. Use the == or Equals() methods exposed by EETypePtr itself.
11097
internal IntPtr RawValue
11198
{

src/coreclr/src/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs

-8
Original file line numberDiff line numberDiff line change
@@ -1071,14 +1071,6 @@ internal static float fabsf(float x)
10711071
[DllImport(RuntimeImports.RuntimeLibrary, ExactSpelling = true)]
10721072
internal static extern unsafe void* memset(byte* mem, int value, nuint size);
10731073

1074-
[MethodImpl(MethodImplOptions.InternalCall)]
1075-
[RuntimeImport(RuntimeLibrary, "RhpArrayCopy")]
1076-
internal static extern bool TryArrayCopy(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length);
1077-
1078-
[MethodImpl(MethodImplOptions.InternalCall)]
1079-
[RuntimeImport(RuntimeLibrary, "RhpArrayClear")]
1080-
internal static extern bool TryArrayClear(Array array, int index, int length);
1081-
10821074
#if TARGET_X86 || TARGET_AMD64
10831075
[DllImport(RuntimeLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
10841076
internal static extern unsafe void RhCpuIdEx(int* cpuInfo, int functionId, int subFunctionId);

0 commit comments

Comments
 (0)