Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert Array.IsSimpleCopy and CanAssignArray type to managed #104103

Merged
merged 15 commits into from
Jul 7, 2024

Conversation

huoyaoyuan
Copy link
Member

Move the two routines back again.

Removes a HELPER_METHOD_FRAME and improves performance for deciding the path.

Benchmark code:

        private object[] objArray_Int = { 123 };
        private object[] objArray_Str = { "abc" };
        private int[] intArray = { 123 };
        private uint[] uintArray = { 123 };
        private byte[] byteArray = { 1 };
        private string[] strArray = { "abc" };
        private int*[] intPointerArray = { (int*)0x1234 };
        private uint*[] uintPointerArray = { (uint*)0x1234 };

        [Benchmark]
        public void SimpleCopy_Derived() => Array.Copy(strArray, objArray_Str, 1);

        [Benchmark]
        public void MustCast() => Array.Copy(objArray_Str, strArray, 1);

        [Benchmark]
        public void SimpleCopy_Primitive() => Array.Copy(intArray, uintArray, 1);

        [Benchmark]
        public void PrimitiveWiden() => Array.Copy(byteArray, intArray, 1);

        [Benchmark]
        public void SimpleCopy_Pointer() => Array.Copy(intPointerArray, uintPointerArray, 1);

Result:

Method Job Toolchain Mean Error StdDev Ratio
SimpleCopy_Derived Job-RIAPVG \PR\corerun.exe 10.092 ns 0.0322 ns 0.0269 ns 0.87
SimpleCopy_Derived Job-PSZNKI \main\corerun.exe 11.586 ns 0.1421 ns 0.1329 ns 1.00
MustCast Job-RIAPVG \PR\corerun.exe 10.382 ns 0.0319 ns 0.0282 ns 0.43
MustCast Job-PSZNKI \main\corerun.exe 23.951 ns 0.1351 ns 0.1198 ns 1.00
SimpleCopy_Primitive Job-RIAPVG \PR\corerun.exe 7.974 ns 0.0679 ns 0.0635 ns 0.69
SimpleCopy_Primitive Job-PSZNKI \main\corerun.exe 11.608 ns 0.0954 ns 0.0893 ns 1.00
PrimitiveWiden Job-RIAPVG \PR\corerun.exe 12.496 ns 0.0779 ns 0.0691 ns 0.44
PrimitiveWiden Job-PSZNKI \main\corerun.exe 28.334 ns 0.5434 ns 0.5083 ns 1.00
SimpleCopy_Pointer Job-RIAPVG \PR\corerun.exe 8.105 ns 0.1383 ns 0.1226 ns 0.80
SimpleCopy_Pointer Job-PSZNKI \main\corerun.exe 10.155 ns 0.0799 ns 0.0747 ns 1.00

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jun 27, 2024
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jun 27, 2024
@huoyaoyuan huoyaoyuan added area-VM-coreclr and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jun 27, 2024
Copy link
Contributor

Tagging subscribers to this area: @mangod9
See info in area-owners.md if you want to be subscribed.

Comment on lines +220 to +226
{
// Only pointers are valid for TypeDesc in array element

// Compatible pointers
if (srcTH.CanCastTo(destTH))
return ArrayAssignType.SimpleCopy;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

If any pointer type goes through the non-simple copy paths, it will result in the same fatal crash.

@jkotas
Copy link
Member

jkotas commented Jun 27, 2024

Could you please take a look at the Mono test failures?

if (result != CastResult.MaybeCast)
return result == CastResult.CanCast;

return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: The first thing that the QCall is going to do is repeat the cache lookup...

Copy link
Member

@jkotas jkotas Jun 28, 2024

Choose a reason for hiding this comment

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

Also, the unmanaged TypeHandle.CanCastTo assumes that some cases are very cheap to check for and it does not bother to add them to the cache. These cases will be much slower here since we are always going to take the QCall transition for them. We should either check for them here and/or add them to cache (similar to how RuntimeTypeHandle::CanCastTo adds them to the cache for the same reasons).

It probably does not matter for the one caller added in this PR, but it may matter for future callers.

Copy link
Member Author

Choose a reason for hiding this comment

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

similar to how RuntimeTypeHandle::CanCastTo adds them to the cache for the same reasons

The methods should probably be combined at managed side. They are doing the exact same things.

Copy link
Member

Choose a reason for hiding this comment

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

Yes (it is fine to do it in a follow up PR).

Copy link
Member Author

Choose a reason for hiding this comment

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

They are doing the exact same things.

In fact they aren't. RuntimeTypeHandle::CanCastTo allows T -> Nullable<T>.

The non-cached cases include nullables, COM and I(Dynamic)Castable interfaces. I don't think specially handling them is worthy, even for future callers.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, you would either need to have a bool flag that controls the special handling or introduce two QCalls with similar implementation.

Copy link
Member

@jkotas jkotas Jun 29, 2024

Choose a reason for hiding this comment

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

Do you plan to do something about this one? (Unifying with RuntimeTypeHandle::CanCastTo should be separate PR, fixing CanCastTo_NoCacheLookup to avoid unnecessary cache lookup should be in this one.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, together with some other cases sharable between RuntimeTypeHandle and MethodTable, like type equivalence.

@huoyaoyuan
Copy link
Member Author

huoyaoyuan commented Jun 28, 2024

Could you please take a look at the Mono test failures?

Yes, I expect the same pointer case should also be disabled for mono.
Well it may be because mono doesn't support compatible pointer case. I don't think enabling it on mono is worthy.

@@ -1597,13 +1598,36 @@ public static void Copy_SourceAndDestinationNeverConvertible_ThrowsArrayTypeMism
Assert.Throws<ArrayTypeMismatchException>(() => sourceArray.CopyTo(destinationArray, (long)destinationArray.GetLowerBound(0)));
}

[Fact]
[SkipOnMono("Mono does not support pointer compatibility in Array.Copy")]
Copy link
Member

Choose a reason for hiding this comment

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

Could you please open an issue on this and disable the test against this issue?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is it something we'd ever want to support on mono?

Copy link
Member

Choose a reason for hiding this comment

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

We do not want to have behavior differences between different runtime. Every behavior difference between runtime is a bug. We may choose to not fix some of these bugs, but I do not see a good reason for it here.

For example, similar Mono-specific issue in casting logic was fixed just a few days ago: #103841

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently the behavior is different among all three runtimes. NativeAOT allows conversion between any pointers.

The current coreclr behavior is really complex to support. Can we make a breaking change instead to support only exactly same pointers?

Copy link
Member

Choose a reason for hiding this comment

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

The current coreclr behavior is really complex to support.

Why is it complex to support?

Can we make a breaking change instead to support only exactly same pointers?

I do not see how we would justify this breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

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

Opened #104197 for mono.

@huoyaoyuan
Copy link
Member Author

Anything remaining for this?

@jkotas
Copy link
Member

jkotas commented Jul 6, 2024

Cleaning up the cache lookups #104103 (comment) ?

@@ -1846,6 +1846,39 @@ extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, Metho
return bResult;
}

extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
extern "C" BOOL QCALLTYPE TypeHandle_CanCastTo(void* fromTypeHnd, void* toTypeHnd)
extern "C" BOOL QCALLTYPE TypeHandle_CanCastToNoCacheLookup(void* fromTypeHnd, void* toTypeHnd)

I think this would be a better name for the QCall to make it clear what it does. Matches convention used for cast helpers (ChkCastAny_NoCacheLookup, etc.)

public bool CanCastTo(TypeHandle destTH)
{
if (m_asTAddr == destTH.m_asTAddr)
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.

I am wondering whether this logic should live in CastHelpers.cs next to all other casting logic, so that it is not missed if there are any bug fixes.

Copy link
Member Author

Choose a reason for hiding this comment

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

They look quite inconsistent...Methods in CastHelpers are HCalls in jithelpers.
BTW does it make sense to convert such methods to QCall, and move out of jithelpers since they are not directly used by JIT?

Copy link
Member

@jkotas jkotas Jul 7, 2024

Choose a reason for hiding this comment

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

does it make sense to convert such methods to QCall

Yes. We want to get rid of all FCalls with HELPER_METHOD_FRAME.

not directly used by JIT

Those are slow path for helpers used by the JIT. Fast paths of those helpers are either in assembly code or in C#.

The split between JIT helpers and other helpers is blurry. It is not unusual for the two to have overlapping logic. We even have methods that are used as JIT helpers, but they are used for other purposes as well. I do not have strong opinions about the best source file split. Anything we come up with will have some downsides.

This can be worked on in a follow up.

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

jkotas commented Jul 7, 2024

/ba-g All failures have known issues opened for them. I am not able to tell why BA is not able to match them

@jkotas jkotas merged commit 7b71281 into dotnet:main Jul 7, 2024
147 of 149 checks passed
@huoyaoyuan huoyaoyuan deleted the array-assign-type branch July 8, 2024 01:51
@github-actions github-actions bot locked and limited conversation to collaborators Aug 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-VM-coreclr community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants