Add TryCopyTo and Set methods to Spans API #5857#6648
Add TryCopyTo and Set methods to Spans API #5857#6648jkotas merged 16 commits intodotnet:SpanOfTfrom
Conversation
|
|
||
| BYTE resultIlCode; | ||
| if (CorTypeInfo::IsObjRef(elementType) || (elementType == ELEMENT_TYPE_VALUETYPE && typeHandle.GetMethodTable()->ContainsPointers())) | ||
| { |
There was a problem hiding this comment.
Is seems to ignore primitives like ELEMENT_TYPE_I4.
I think the whole check should be just for ContainsPointers.
There was a problem hiding this comment.
@omariom I am not sure. I think that it will execute the else case in that scenario and will always return false.
There was a problem hiding this comment.
I've confused the branches )
But what about just ContainsPointers? Isn't it enough?
There was a problem hiding this comment.
I think the best way to write is:
MethodTable * pMT = typeHandle.GetMethodTable();
if (!pMT->IsValueType() || pMT->ContainsPointers())
{
...
}
But what about just
ContainsPointers?
It does not work for non-valuetype types. E.g. class Foo { int x; }. MethodTable::ContainsPointers returns false, but we want this primitive to return true.
There was a problem hiding this comment.
It may be worth adding a comment explaining difference between MethodTable::ContainsPointers and ContainsReferences.
For now, I have been running it manually: Yes, there is a work to do in getting hooked up in the regular test runs. I have not done it because of the tests are compiled against contracts by default. Since Span is not in contracts yet, it would require some special sauce to make it work. |
|
@adamsitnik Thank you for getting this started! It looks great to me, modulo comments. |
|
@jkotas Thanks for all the comments! Today I added some detailed tests. Currently all of them fail, will investigate tomorrow. |
| static void Main() | ||
| { | ||
| Test(() => CanAccessItemsViaIndexer(), "CanAccessItemsViaIndexer"); | ||
|
|
|
@jkotas I got stucked again. I have debugged the CLR but without luck. The problem: it seems that slices do not work well with the reference types even without my changes. Sample failing test: class ReferenceType
{
internal byte Value;
public ReferenceType(byte value) { Value = value; }
}
private static void ReferenceTypesAreSupported()
{
var underlyingArray = new ReferenceType[] { new ReferenceType(0), new ReferenceType(1), new ReferenceType(2) };
var slice = new Span<ReferenceType>(underlyingArray);
for (int i = 0; i < underlyingArray.Length; i++)
{
AssertTrue(underlyingArray[i].Value == slice[i].Value, "Values are different");
AssertTrue(object.ReferenceEquals(underlyingArray[i], slice[i]), "References are broken");
}
}result: "test has failed with exception: Object reference not set to an instance of an object." Jan could you take a look at this? This is blocking for me because I am not sure if my copying tests fail because of general Slice issue or my code has a bug. |
|
@adamsitnik |
|
@omariom This is tricky ;) Slice indexer getter: JitHelper.cs constains the declarations: static internal ref T GetByRef<T>(ref IntPtr byref)
static internal void SetByRef<T>(out IntPtr byref, ref T value)
static internal ref T AddByRef<T>(ref T pointer, int count)
static internal bool ByRefEquals<T>(ref T refA, ref T refB)Which are replaced by the actual CIL at runtime by CLR with help of jitinterface.cpp. GetByRef is |
|
Thank you for tracing down the bug in handling of reference types. I have fixed it in #6954. |
|
@jkotas Thanks for such fast response! Everytime I read your commits I learn something new. Should I adopt my SizeOf() to the new way that you have implemented by using the metadata token and |
I think so. The previous implementation was not actually thread safe. The IL is in static variable - it is not ok for different threads to end up with different IL. |
|
For same reason, |
Conflicts: src/mscorlib/src/System/Runtime/CompilerServices/jithelpers.cs src/vm/jitinterface.cpp src/vm/mscorlib.h tests/src/CoreMangLib/system/span/BasicSpanTest.cs
…whole loop, it did not work well with structs with pointers
|
@jkotas This commit has fixed my test for copying value types with reference. Now all tests are passing! Could you do one more code review? Then I simply copy all the changes to ReadOnlySpan and we should be able to merge. |
The original code should work fine. Is this commit working around a bug? |
src/mscorlib/src/System/Span.cs
Outdated
| get { return _length; } | ||
| } | ||
|
|
||
| internal uint SizeInBytes |
There was a problem hiding this comment.
Using uint for byte sizes is problematic because it can overflow on 64-bit platforms. You may want to use nuint like here: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Buffer.cs#L18
Conflicts: src/mscorlib/src/System/Span.cs
Thanks for the explanation! I added unified contracts for all methods. The other minor (change) was to add the |
src/mscorlib/src/System/Span.cs
Outdated
| internal static class SpanContracts | ||
| { | ||
| internal static void RequiresIndexInRange(int index, int length) { | ||
| if ((uint)index >= (uint)length) |
There was a problem hiding this comment.
I do not think that having contract methods on the side is the preferred corefx coding style. We have done experiments with code contracts in the past, so some old code may still look similar to this ... but it was abandoned as not a very good idea.
There was a problem hiding this comment.
@jkotas Could you provide me a link with a sample that has contracts implemented in the right way?
There was a problem hiding this comment.
Regular if-checks. Like it used to be before 496e344
|
LGTM modulo last few comments. @KrzysztofCwalina Could you please take a look as well? |
| /// <param name="destination">An unmanaged pointer to memory.</param> | ||
| /// <param name="elementsCount">The number of T elements the memory contains.</param> | ||
| [CLSCompliant(false)] | ||
| public unsafe bool TryCopyTo(void* destination, int elementsCount) |
There was a problem hiding this comment.
Should we force the called to state how large is the destination (in bytes)?
There was a problem hiding this comment.
@KrzysztofCwalina From my perspective, as the end user having elementsCount is more usefull than sizeInBytes. Mostly because you need the sizeof !!T to calculate the sizeInBytes. It's part of IL but not available in C#. I have added the SizeOf<T>() to JitHelpers so CLR can take care of this part instead of leaving it for the end user.
What is your opinion on that?
There was a problem hiding this comment.
To me it's about ensuring that the caller does not cause buffer overruns because they misjudged the size of the buffer needed to fit the elements. It's much less likely that they don't know how many bytes they have in the destination.
@jkotas, any opinions on this?
There was a problem hiding this comment.
I think it is confusing either way. It may be better to not have this unsafe API at all - the fewer APIs that take unmanaged pointers the better, and instead make people to use the other overload that takes Span - it more clearly expresses the intent:
TryCopyTo(Span<int>(pBuffer, nElements)).
|
@jkotas @KrzysztofCwalina overlapping support added |
|
@jkotas @KrzysztofCwalina I have inlined the code contracts as Jan suggested, also added info about exceptions to summaries. Unanswered questions:
Also new thing: @KrzysztofCwalina can I add |
|
@KrzysztofCwalina @jkotas could you answer the questions and/or accept the PR? |
|
I'm going to answer those questions even though it probably doesn't matter:
Yes. Matches with the rest of the CLR nomenclature.
Remove it. Span all the things!
Yes. It matches this dotnet/corefxlab@97afeb6. |
|
@davidfowl thanks! |
You mean array to span? |
@omariom yes, you are right. I will update the description |
|
@jkotas @KrzysztofCwalina Done. Please let me know if it requires any further changes. |
|
looks good to me! |
|
Thank you! |
Hello!
@jkotas @KrzysztofCwalina Could you review the code and tell if everything is ok before I implement Set method and copy the code for ReadOnlySpan?
Jan I have seen that you added some basic tests for Span, but it seems that they are not executed during build with tests included. Could you add it to the SpanOfT branch or tell me how can I quickly execute this tests?