diff options
author | Michal Strehovský <MichalStrehovsky@users.noreply.github.com> | 2017-05-15 23:06:44 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-15 23:06:44 +0300 |
commit | 9aa607be455d6d3b013fdb8caaabda600d649fde (patch) | |
tree | e07a068dd27917c4793e018a22633f31015a025d | |
parent | 692b07d0946a9df4918199d4287ce957c3e81521 (diff) |
Finish support for rank 1 mdarrays (#3610)
Three parts:
1. Update casting logic to allow casting SzArray to rank 1 MdArray (but
not the other way around)
2. Update MdArray rank 1 methods to be able to operate on SzArrays
3. Update array creation path to emulate the behavior where allocating
rank 1 MdArray with 0 lower bounds actually gives you an SzArray
Third bullet point makes this the most annoying, because we can't
reliably support `newobj instance void int32[0...]::.ctor(int32)`
without hitting the type loader. I was also considering adding an
optional field on Rank 1 MdArrays that lets you get to the SzArray's
EEType from it. It might be better.
Fixes #3331.
9 files changed, 90 insertions, 46 deletions
diff --git a/src/Common/src/TypeSystem/Common/CastingHelper.cs b/src/Common/src/TypeSystem/Common/CastingHelper.cs index ebfe39515..b7576923c 100644 --- a/src/Common/src/TypeSystem/Common/CastingHelper.cs +++ b/src/Common/src/TypeSystem/Common/CastingHelper.cs @@ -78,6 +78,14 @@ namespace Internal.TypeSystem // Casting array to something else (between SzArray and Array, for example)? if (thisType.Category != otherType.Category) { + // An SzArray is castable to MdArray rank 1. We follow the same casting rules as SzArray to SzArray. + if (thisType.Category == TypeFlags.SzArray + && otherType.Category == TypeFlags.Array + && ((ArrayType)otherType).Rank == 1) + { + return thisType.CanCastParamTo(((ArrayType)otherType).ParameterType, protect); + } + return false; } diff --git a/src/Common/src/TypeSystem/IL/Stubs/ArrayMethodILEmitter.cs b/src/Common/src/TypeSystem/IL/Stubs/ArrayMethodILEmitter.cs index c9b4c6ecb..4e8e7a171 100644 --- a/src/Common/src/TypeSystem/IL/Stubs/ArrayMethodILEmitter.cs +++ b/src/Common/src/TypeSystem/IL/Stubs/ArrayMethodILEmitter.cs @@ -142,6 +142,44 @@ namespace Internal.IL.Stubs } } + // Methods on Rank 1 MdArray need to be able to handle `this` that is an SzArray + // because SzArray is castable to Rank 1 MdArray (but not the other way around). + + ILCodeLabel rangeCheckDoneLabel = null; + if (_rank == 1) + { + TypeDesc objectType = context.GetWellKnownType(WellKnownType.Object); + TypeDesc eetypePtrType = context.SystemModule.GetKnownType("System", "EETypePtr"); + ILLocalVariable thisEEType = _emitter.NewLocal(eetypePtrType); + + codeStream.EmitLdArg(0); + codeStream.Emit(ILOpcode.call, _emitter.NewToken(objectType.GetKnownMethod("get_EETypePtr", null))); + codeStream.EmitStLoc(thisEEType); + codeStream.EmitLdLoca(thisEEType); + codeStream.Emit(ILOpcode.call, + _emitter.NewToken(eetypePtrType.GetKnownMethod("get_IsSzArray", null))); + + ILCodeLabel notSzArrayLabel = _emitter.NewCodeLabel(); + codeStream.Emit(ILOpcode.brfalse, notSzArrayLabel); + + // We have an SzArray - do the bounds check differently + EmitLoadInteriorAddress(codeStream, pointerSize); + codeStream.Emit(ILOpcode.dup); + codeStream.Emit(ILOpcode.ldind_i4); + codeStream.EmitLdArg(argStartOffset); + codeStream.EmitStLoc(totalLocalNum); + codeStream.EmitLdLoc(totalLocalNum); + codeStream.Emit(ILOpcode.ble_un, rangeExceptionLabel); + + codeStream.EmitLdc(pointerSize); + codeStream.Emit(ILOpcode.add); + + rangeCheckDoneLabel = _emitter.NewCodeLabel(); + codeStream.Emit(ILOpcode.br, rangeCheckDoneLabel); + + codeStream.EmitLabel(notSzArrayLabel); + } + for (int i = 0; i < _rank; i++) { // The first two fields are EEType pointer and total length. Lengths for each dimension follows. @@ -174,6 +212,9 @@ namespace Internal.IL.Stubs int firstElementOffset = (2 * pointerSize + 2 * _rank * sizeof(int)); EmitLoadInteriorAddress(codeStream, firstElementOffset); + if (rangeCheckDoneLabel != null) + codeStream.EmitLabel(rangeCheckDoneLabel); + codeStream.EmitLdLoc(totalLocalNum); int elementSize = _elementType.GetElementSize().AsInt; diff --git a/src/ILCompiler.TypeSystem/tests/CastingTests.cs b/src/ILCompiler.TypeSystem/tests/CastingTests.cs index b2771234c..573bc1822 100644 --- a/src/ILCompiler.TypeSystem/tests/CastingTests.cs +++ b/src/ILCompiler.TypeSystem/tests/CastingTests.cs @@ -73,6 +73,7 @@ namespace TypeSystemTests TypeDesc shortBasedEnumType = _testModule.GetType("Casting", "ShortBasedEnum"); Assert.True(intType.MakeArrayType().CanCastTo(uintType.MakeArrayType())); + Assert.True(intType.MakeArrayType().CanCastTo(uintType.MakeArrayType(1))); Assert.False(intType.CanCastTo(uintType)); Assert.True(byteType.MakeArrayType().CanCastTo(sbyteType.MakeArrayType())); @@ -121,7 +122,9 @@ namespace TypeSystemTests TypeDesc stringSzArrayType = stringType.MakeArrayType(); TypeDesc objectSzArrayType = objectType.MakeArrayType(); - Assert.False(intSzArrayType.CanCastTo(intArray1Type)); + Assert.True(intSzArrayType.CanCastTo(intArray1Type)); + Assert.False(intArray1Type.CanCastTo(intSzArrayType)); + Assert.False(intArray1Type.CanCastTo(intArray2Type)); Assert.True(intSzArrayType.CanCastTo(arrayType)); diff --git a/src/Runtime.Base/src/System/Runtime/TypeCast.cs b/src/Runtime.Base/src/System/Runtime/TypeCast.cs index d7a549a4b..62dea8448 100644 --- a/src/Runtime.Base/src/System/Runtime/TypeCast.cs +++ b/src/Runtime.Base/src/System/Runtime/TypeCast.cs @@ -219,8 +219,16 @@ namespace System.Runtime // compare the array types structurally - if (pObjType->ParameterizedTypeShape == pTargetType->ParameterizedTypeShape && - CastCache.AreTypesAssignableInternal(pObjType->RelatedParameterType, pTargetType->RelatedParameterType, + if (pObjType->ParameterizedTypeShape != pTargetType->ParameterizedTypeShape) + { + // If the shapes are different, there's one more case to check for: Casting SzArray to MdArray rank 1. + if (!pObjType->IsSzArray || pTargetType->ArrayRank != 1) + { + return null; + } + } + + if (CastCache.AreTypesAssignableInternal(pObjType->RelatedParameterType, pTargetType->RelatedParameterType, AssignmentVariation.AllowSizeEquivalence)) { return obj; diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 5e29b5907..67c6378c8 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -147,6 +147,14 @@ namespace Internal.Runtime.Augments } } + if (lengths.Length == 1) + { + // We just checked above that all lower bounds are zero. In that case, we should actually allocate + // a new SzArray instead. + RuntimeTypeHandle elementTypeHandle = new RuntimeTypeHandle(typeHandleForArrayType.ToEETypePtr().ArrayElementType); + return Array.CreateInstance(Type.GetTypeFromHandle(elementTypeHandle), lengths[0]); + } + // Create a local copy of the lenghts that cannot be motified by the caller int* pLengths = stackalloc int[lengths.Length]; for (int i = 0; i < lengths.Length; i++) diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ArrayHelpers.cs b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ArrayHelpers.cs index 1f76a8b00..8b27d139f 100644 --- a/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ArrayHelpers.cs +++ b/src/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ArrayHelpers.cs @@ -41,6 +41,13 @@ namespace Internal.Runtime.CompilerHelpers return ret; } + else if (nDimensions == 1) + { + // Multidimensional array of rank 1 with 0 lower bounds gets actually allocated + // as an SzArray. SzArray is castable to MdArray rank 1. + RuntimeTypeHandle elementTypeHandle = new RuntimeTypeHandle(eeType.ArrayElementType); + return Array.CreateInstance(Type.GetTypeFromHandle(elementTypeHandle), pDimensions[0]); + } else { // Multidimensional arrays have two ctors, one with and one without lower bounds diff --git a/src/System.Private.CoreLib/src/System/Array.CoreRT.cs b/src/System.Private.CoreLib/src/System/Array.CoreRT.cs index 31168971e..aaa4af523 100644 --- a/src/System.Private.CoreLib/src/System/Array.CoreRT.cs +++ b/src/System.Private.CoreLib/src/System/Array.CoreRT.cs @@ -181,15 +181,7 @@ namespace System elementType = elementType.UnderlyingSystemType; - if (lengths.Length == 1 && lowerBounds[0] == 0) - { - int length = lengths[0]; - return CreateSzArray(elementType, length); - } - else - { - return CreateMultiDimArray(elementType, lengths, lowerBounds); - } + return CreateMultiDimArray(elementType, lengths, lowerBounds); } private static Array CreateSzArray(Type elementType, int length) @@ -990,6 +982,13 @@ namespace System Debug.Assert(eeType.IsArray && !eeType.IsSzArray); Debug.Assert(rank == eeType.ArrayRank); + // Code below assumes 0 lower bounds. MdArray of rank 1 with zero lower bounds should never be allocated. + // The runtime always allocates an SzArray for those: + // * newobj instance void int32[0...]::.ctor(int32)" actually gives you int[] + // * int[] is castable to int[*] to make it mostly transparent + // The callers need to check for this. + Debug.Assert(rank != 1); + ulong totalLength = 1; bool maxArrayDimensionLengthOverflow = false; @@ -1679,11 +1678,9 @@ namespace System } } -#if CORERT public class MDArray { public const int MinRank = 1; public const int MaxRank = 32; } -#endif } diff --git a/src/System.Private.CoreLib/src/System/MDArray.cs b/src/System.Private.CoreLib/src/System/MDArray.cs index 5614e9b1f..a7256bdc7 100644 --- a/src/System.Private.CoreLib/src/System/MDArray.cs +++ b/src/System.Private.CoreLib/src/System/MDArray.cs @@ -24,12 +24,6 @@ namespace System // The desktop CLR supports arrays of up to 32 dimensions so that provides // an upper limit on how much this needs to be built out. - public class MDArray - { - public const int MinRank = 2; - public const int MaxRank = 32; - } - [StructLayout(LayoutKind.Sequential)] public class MDArrayRank2<T> { diff --git a/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/TypeInfos/RuntimeArrayTypeInfo.cs b/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/TypeInfos/RuntimeArrayTypeInfo.cs index 252e1a05e..feb436d87 100644 --- a/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/TypeInfos/RuntimeArrayTypeInfo.cs +++ b/src/System.Private.Reflection.Core/src/System/Reflection/Runtime/TypeInfos/RuntimeArrayTypeInfo.cs @@ -65,34 +65,12 @@ namespace System.Reflection.Runtime.TypeInfos InvokerOptions.AllowNullThis | InvokerOptions.DontWrapException, delegate (Object _this, Object[] args) { - if (rank == 1) - { - // Legacy: This seems really wrong in the rank1-multidim case (as it's a case of a synthetic constructor that's declared on T[*] returning an instance of T[]) - // This is how the desktop behaves, however. - - int count = (int)(args[0]); - - RuntimeTypeInfo vectorType; - if (multiDim) - { - vectorType = arrayType.InternalRuntimeElementType.GetArrayType(); - } - else - { - vectorType = arrayType; - } - - return ReflectionCoreExecution.ExecutionEnvironment.NewArray(vectorType.TypeHandle, count); - } - else + int[] lengths = new int[rank]; + for (int i = 0; i < rank; i++) { - int[] lengths = new int[rank]; - for (int i = 0; i < rank; i++) - { - lengths[i] = (int)(args[i]); - } - return ReflectionCoreExecution.ExecutionEnvironment.NewMultiDimArray(arrayType.TypeHandle, lengths, null); + lengths[i] = (int)(args[i]); } + return ReflectionCoreExecution.ExecutionEnvironment.NewMultiDimArray(arrayType.TypeHandle, lengths, null); } ); } |