Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

[Interpreter] Array #6965

Merged
merged 13 commits into from
Feb 12, 2019
Merged

[Interpreter] Array #6965

merged 13 commits into from
Feb 12, 2019

Conversation

tonerdo
Copy link
Contributor

@tonerdo tonerdo commented Feb 7, 2019

This PR adds support for single-dimension zero-based arrays. It adds interpretation of the following opcodes:

  • newarr
  • ldlen
  • stelem.*
  • ldelem.*

which allows methods like the following to be interpreted:

public static int[] GetArray()
{
    int[] array = new int[5];
    array[0] = 23;
    array[1] = 34;
    array[2] = 43;
    array[3] = 122;
    array[4] = 4;
    return array;
}

public static int GetArrayElement(int[] array, int index)
{
    return array[index];
}

public static int GetArrayLength(int l)
{
    return new int[l].Length;
}

Debug.Assert(length >= 0);

TypeDesc arrayType = ((TypeDesc)_methodIL.GetObject(token)).MakeArrayType();
Array array = RuntimeAugments.NewArray(arrayType.GetRuntimeTypeHandle(), length);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work for non primitive types. Something like new DateTime[10] fails because the runtime is unable to find the RuntimeTypeHandle for DateTime[] even though there exists one for DateTime. Is there a way to probe the metadata of the interpreted assembly (and its dependencies) for runtime type information?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should work for arbitrary reference type arrays - value types are harder.

You should be able to work around by adding this array to RD.XML or adding typeof(DateTime[]) somewhere in the code that hosts the interpreter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm....didn't work when I tried it with an Exception type. I'll just add a TODO to add support for arbitrary types

array.SetValue((sbyte)valueItem.AsInt32(), index);
break;
case ILOpcode.stelem_i2:
array.SetValue((short)valueItem.AsInt32(), index);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would've preferred to use the generic type Array<T> to prevent unnecessary boxing but when I do, ILC fails to compile the CoreRT console app with the following error:

Assertion Failed
  Asking for Array<T> EEType

     at ILCompiler.DependencyAnalysis.EETypeNode.OnMarked(NodeFactory context) in C:\Users\toni\Workspace\corert\src\ILCompiler.Compiler\src\Compiler\DependencyAnalysis\EETypeNode.c
s:line 1087
     at ILCompiler.DependencyAnalysisFramework.DependencyNodeCore`1.CallOnMarked(DependencyContextType context) in C:\Users\toni\Workspace\corert\src\ILCompiler.DependencyAnalysisFr
amework\src\DependencyNodeCore.cs:line 136
     at ILCompiler.DependencyAnalysisFramework.DependencyAnalyzer`2.AddToMarkStack(DependencyNodeCore`1 node, String reason, DependencyNodeCore`1 reason1, DependencyNodeCore`1 reaso
n2) in C:\Users\toni\Workspace\corert\src\ILCompiler.DependencyAnalysisFramework\src\DependencyAnalyzer.cs:line 313
     at ILCompiler.DependencyAnalysisFramework.DependencyAnalyzer`2.GetStaticDependenciesImpl(DependencyNodeCore`1 node) in C:\Users\toni\Workspace\corert\src\ILCompiler.DependencyA
nalysisFramework\src\DependencyAnalyzer.cs:line 150
     at ILCompiler.DependencyAnalysisFramework.DependencyAnalyzer`2.ComputeMarkedNodes() in C:\Users\toni\Workspace\corert\src\ILCompiler.DependencyAnalysisFramework\src\DependencyA
nalyzer.cs:line 266
     at ILCompiler.RyuJitCompilation.CompileInternal(String outputFile, ObjectDumper dumper) in C:\Users\toni\Workspace\corert\src\ILCompiler.RyuJit\src\Compiler\RyuJitCompilation.c
s:line 40
     at ILCompiler.Compilation.ILCompiler.ICompilation.Compile(String outputFile, ObjectDumper dumper) in C:\Users\toni\Workspace\corert\src\ILCompiler.Compiler\src\Compiler\Compila
tion.cs:line 387
     at ILCompiler.Program.Run(String[] args) in C:\Users\toni\Workspace\corert\src\ILCompiler\src\Program.cs:line 575
     at ILCompiler.Program.Main(String[] args) in C:\Users\toni\Workspace\corert\src\ILCompiler\src\Program.cs:line 731

The runtime builds successfully

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array<T> is just a type that we have in the corelib so that we can write methods that support the generic interfaces on arrays. We take it's vtable and use it as the vtable of T[]. The type itself never exists. Even if we were to remove the check in the compiler, you wouldn't be able to cast to it at runtime because Array<T> is different from T[].

You can call GetRawArrayData() to get a byref to the first element of the array and then use Unsafe class to do pointer arithmetic and writes/reads. Note that it will require adding bounds checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetArrayData is internal to System.Private.CoreLib so I can't use it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind making it public. The other option is to expose it through the RuntimeAugments class which is how we do it for other corelib internal APIs (reasons for RuntimeAugments are historic but we still do it for consistency).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what you have in mind?

ref byte start = ref RuntimeAugments.GetRawDataForArray(array);
ref byte position = ref Unsafe.Add(ref start, index);

// Read from int[]
Unsafe.Read<int>(ref position);

// Write to int[]
Unsafe.Write<int>(ref position, 23);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what you have in mind?

We need to multiply index by element size (available as Array.ElementSize in the CoreLib), but yes, that's the idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use Unsafe.As to cast to the right element type and avoid the Unsafe.Read and Unsafe.Write. I don't know whether Unsafe.Write does the right write barriers if the element is a reference type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know whether Unsafe.Write does the right write barriers if the element is a reference type.

It works fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Array.ElementSize is definitely what I was missing

index = indexItem.AsInt32();
break;
case StackValueKind.NativeInt:
index = (int)indexItem.AsNativeInt();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs bounds check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the conversion from IntPtr to int or the the value of index relating to the array length (I already have this in my local branch)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of them.

Unsafe.Write(ref position, valueItem.AsDouble());
break;
case ILOpcode.stelem_ref:
Unsafe.Write(ref position, valueItem.AsObjectRef());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs assignability check. It should end up calling System.Runtime.TypeCast.StelemRef

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either need to expose the method to do this via RuntimeAugments; or cast the array to object[] and assign it (Array.SetValue does this).

case StackValueKind.NativeInt:
{
long value = (long)indexItem.AsNativeInt();
Debug.Assert(value >= int.MinValue && value <= int.MaxValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should throw IndexOutOfRangeException, not assert.

break;
}

if (index < 0 || index >= array.Length)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This can be if ((uint)index >= (uint)array.Length) to make it a bit faster. We use this trick frequently for array bound checks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this check can be inside GetSzArrayElementAddress.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this trick frequently for array bound checks.

Oh I saw it on Dictionary, is there also some special jit treatment if it recognize pattern?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is one comparison instead of two. It would be nice if the JIT recognized the two comparisons and converted them into one, but we are not there yet.

@@ -158,6 +164,17 @@ public static unsafe Array NewMultiDimArray(RuntimeTypeHandle typeHandleForArray
return Array.NewMultiDimArray(typeHandleForArrayType.ToEETypePtr(), pLengths, lengths.Length);
}

public static void StelemRef(Array array, int index, object obj)
{
TypeCast.StelemRef(array, index, obj);
Copy link
Member

@jkotas jkotas Feb 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that this won't compile in the closed source ProjectN build (the Runtime,Base is not part of CoreLib in that build). @MichalStrehovsky Do you agree?

I think it would be easier to delete this method; and replace the calls with Unsafe.As<Object[]>(array)[index] = obj;.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it wouldn't compile. Unsafe.As sounds good!

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@jkotas jkotas merged commit d8d1a91 into dotnet:master Feb 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants