Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Add TryCopyTo and Set methods to Spans API #5857#6648

Merged
jkotas merged 16 commits intodotnet:SpanOfTfrom
adamsitnik:spanFastCopy
Sep 12, 2016
Merged

Add TryCopyTo and Set methods to Spans API #5857#6648
jkotas merged 16 commits intodotnet:SpanOfTfrom
adamsitnik:spanFastCopy

Conversation

@adamsitnik
Copy link
Member

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?


BYTE resultIlCode;
if (CorTypeInfo::IsObjRef(elementType) || (elementType == ELEMENT_TYPE_VALUETYPE && typeHandle.GetMethodTable()->ContainsPointers()))
{
Copy link

@omariom omariom Aug 8, 2016

Choose a reason for hiding this comment

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

Is seems to ignore primitives like ELEMENT_TYPE_I4.
I think the whole check should be just for ContainsPointers.

Copy link
Member Author

Choose a reason for hiding this comment

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

@omariom I am not sure. I think that it will execute the else case in that scenario and will always return false.

Copy link

Choose a reason for hiding this comment

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

I've confused the branches )
But what about just ContainsPointers? Isn't it enough?

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

It may be worth adding a comment explaining difference between MethodTable::ContainsPointers and ContainsReferences.

@jkotas
Copy link
Member

jkotas commented Aug 8, 2016

tell me how can I quickly execute this tests?

For now, I have been running it manually:

d:\coreclr\bin\Product\Windows_NT.x64.Debug>csc /noconfig /nostdlib /r:System.Private.CoreLib.dll \coreclr\tests\src\CoreMangLib\system\span\BasicSpanTest.cs

d:\coreclr\bin\Product\Windows_NT.x64.Debug>corerun BasicSpanTest.exe

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.

@jkotas
Copy link
Member

jkotas commented Aug 8, 2016

@adamsitnik Thank you for getting this started! It looks great to me, modulo comments.

@adamsitnik
Copy link
Member Author

@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");

Copy link

Choose a reason for hiding this comment

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

Use nameof here?

@adamsitnik
Copy link
Member Author

@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.

@omariom
Copy link

omariom commented Aug 27, 2016

@adamsitnik
what code Slice indexer getter generates here?

@adamsitnik
Copy link
Member Author

adamsitnik commented Aug 28, 2016

@omariom This is tricky ;)

Slice indexer getter:
JitHelpers.AddByRef(ref JitHelpers.GetByRef<T>(ref _rawPointer), index);

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 CEE_LDARG_0, CEE_LDIND_I, CEE_RET
AddByRef is

CEE_LDARG_1, 
CEE_LDC_I4 size,
CEE_CONV_I, 
CEE_MUL, 
CEE_LDARG_0, 
CEE_ADD, 
CEE_RET

@jkotas
Copy link
Member

jkotas commented Aug 29, 2016

Thank you for tracing down the bug in handling of reference types. I have fixed it in #6954.

@adamsitnik
Copy link
Member Author

@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
CEE_SIZEOF; instr? Previously I had no idea how to obtain !!T

@jkotas
Copy link
Member

jkotas commented Aug 29, 2016

Should I adopt my SizeOf() to the new way that you have implemented by using the metadata token and CEE_SIZEOF?

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.

@jkotas
Copy link
Member

jkotas commented Aug 29, 2016

For same reason, ContainsReferences should be done by having two static const byte arrays - one for returning false and one for returning true.

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
@adamsitnik
Copy link
Member Author

adamsitnik commented Aug 29, 2016

@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.

@jkotas
Copy link
Member

jkotas commented Aug 29, 2016

This commit has fixed my test for copying value types with reference.

The original code should work fine. Is this commit working around a bug?

get { return _length; }
}

internal uint SizeInBytes
Copy link
Member

@jkotas jkotas Aug 29, 2016

Choose a reason for hiding this comment

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

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

@adamsitnik
Copy link
Member Author

Actually, the original code had a bug - it was de-referencing the managed pointer. It is necessary to sprinkle ref throughout to make this work like this:

Thanks for the explanation!

I added unified contracts for all methods. The other minor (change) was to add the Slice(System.Int32) implementation as requested in #5857. I think that the code is ready,

internal static class SpanContracts
{
internal static void RequiresIndexInRange(int index, int length) {
if ((uint)index >= (uint)length)
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jkotas Could you provide me a link with a sample that has contracts implemented in the right way?

Copy link
Member

@jkotas jkotas Aug 31, 2016

Choose a reason for hiding this comment

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

Regular if-checks. Like it used to be before 496e344

@jkotas
Copy link
Member

jkotas commented Aug 30, 2016

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)
Copy link
Member

Choose a reason for hiding this comment

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

Should we force the called to state how large is the destination (in bytes)?

Copy link
Member Author

Choose a reason for hiding this comment

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

@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?

Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Member

@jkotas jkotas Aug 31, 2016

Choose a reason for hiding this comment

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

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)).

@adamsitnik
Copy link
Member Author

@jkotas @KrzysztofCwalina overlapping support added

@adamsitnik
Copy link
Member Author

adamsitnik commented Sep 2, 2016

@jkotas @KrzysztofCwalina I have inlined the code contracts as Jan suggested, also added info about exceptions to summaries.

Unanswered questions:

  1. Should I rename CreateArray to ToArray?
  2. Should I remove unsafe bool TryCopyTo(void* destination, int elementsCount) or change elementsCount to sizeInBytes?
  3. Should I add implicit cast operator of Array to Span?

Also new thing: @KrzysztofCwalina can I add Cast<T, TU> method implementation to Span as it is in corefxlab? With CLR's helpers I could constrain T & TU to be blittable or value types without references (runtime chcecks).

@adamsitnik
Copy link
Member Author

@KrzysztofCwalina @jkotas could you answer the questions and/or accept the PR?

@davidfowl
Copy link
Member

I'm going to answer those questions even though it probably doesn't matter:

Should I rename CreateArray to ToArray?

Yes. Matches with the rest of the CLR nomenclature. ToArray() allocates which is the reason it's called CreateArray() (to make sure you know what you're doing).

Should I remove unsafe bool TryCopyTo(void* destination, int elementsCount) or change elementsCount to sizeInBytes?

Remove it. Span all the things!

Should I add implicit cast operator of Span to Array?

Yes. It matches this dotnet/corefxlab@97afeb6.

@adamsitnik
Copy link
Member Author

@davidfowl thanks!

@omariom
Copy link

omariom commented Sep 12, 2016

Should I add implicit cast operator of Span to Array?

You mean array to span?

@adamsitnik
Copy link
Member Author

You mean array to span?

@omariom yes, you are right. I will update the description

@adamsitnik
Copy link
Member Author

@jkotas @KrzysztofCwalina Done. Please let me know if it requires any further changes.

@KrzysztofCwalina
Copy link
Member

looks good to me!

@jkotas jkotas merged commit d83919c into dotnet:SpanOfT Sep 12, 2016
@jkotas
Copy link
Member

jkotas commented Sep 12, 2016

Thank you!

jkotas pushed a commit to jkotas/coreclr that referenced this pull request Oct 29, 2016
jkotas pushed a commit to jkotas/coreclr that referenced this pull request Oct 29, 2016
jkotas pushed a commit to jkotas/coreclr that referenced this pull request Oct 29, 2016
jkotas pushed a commit to jkotas/coreclr that referenced this pull request Oct 29, 2016
jkotas pushed a commit to jkotas/coreclr that referenced this pull request Oct 31, 2016
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.

7 participants