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

Use architecture independent code paths in Array #108449

Merged
merged 2 commits into from
Oct 5, 2024

Conversation

joncham
Copy link
Contributor

@joncham joncham commented Oct 1, 2024

We at Unity have in-progress work towards a modern .NET ecosystem enabled by adopting CoreCLR in lieu of Mono and updating IL2CPP to support the .NET BCL. I am investigating how architecture specific code paths can be reduced if not completely removed in the .NET BCL. I understand this architecture agnostic approach is not the direction of .NET in general, but I am hoping to validate how to best approach this without maintaining a completely separate fork.

The current BCL, more specifically System.Private.Corlib, has a non-trivial number of preprocessor checks for architecture/bit specific code. At Unity, our IL2CPP AOT compiler has historically consumed the Mono BCL which was architecture agnostic. This enabled a number of benefits for us when targeting multiple architectures such as reduced iteration time via a single managed linker step and transpile step to C++, and reduced deployment size via sharing our binary metadata file between architectures (produced during transpile).

In this preliminary PR, I include two commits with possible approaches that I wanted to validate before submitting larger PRs. In the first commit, native sized integers (nint & nuint) are used rather than a preprocessor check and int & long types. In the second commit, the int & long types remain in use but are switched via a IntPtr.Size check.

A few questions:

  1. Any preference on either commit as a general approach when possible?
  2. What metrics should be evaluated when making such changes? Are there sufficient performance and size tests run as part of the automated PR process?
  3. In more complex scenarios where the preprocessor checks are used, does a new architecture independent target, e.g. TARGET_ARCH_NEUTRAL, make sense to introduce and use alongside the existing code paths? I would expect this path to contain both implementations with a runtime check similar to commit 2 in this PR.

Thanks for any feedback!

@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 Oct 1, 2024
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 1, 2024
Comment on lines 931 to 934
result = GenericBinarySearch<nint>(array, adjustedIndex, length, value);
break;
case CorElementType.ELEMENT_TYPE_U:
result = GenericBinarySearch<nuint>(array, adjustedIndex, length, value);
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
result = GenericBinarySearch<nint>(array, adjustedIndex, length, value);
break;
case CorElementType.ELEMENT_TYPE_U:
result = GenericBinarySearch<nuint>(array, adjustedIndex, length, value);
result = (IntPtr.Size == 4) ? GenericBinarySearch<int>(array, adjustedIndex, length, value) : GenericBinarySearch<long>(array, adjustedIndex, length, value);
break;
case CorElementType.ELEMENT_TYPE_U:
result = (IntPtr.Size == 4) ? GenericBinarySearch<uint>(array, adjustedIndex, length, value) : GenericBinarySearch<ulong>(array, adjustedIndex, length, value);

Same pattern as below - avoids nint/nuint generic instantiations.

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 update all #if TARGET_32/64BIT in Array.cs to this pattern? We can merge this PR after that.

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 pushed change to take this approach uniformly in this file.

@jkotas jkotas added area-Infrastructure-libraries and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Oct 1, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

@jkotas
Copy link
Member

jkotas commented Oct 1, 2024

Any preference on either commit as a general approach when possible?

I think the runtime check approach is better in this specific case. It avoids code size hit from extra generic instantiations. (The check will be turned into no-op via trimmer substitutions or by the compiler, so it is not really a runtime check.)

What metrics should be evaluated when making such changes?

Perf (throughput, memory consumption, code size) and maintainability.

Are there sufficient performance and size tests run as part of the automated PR process?

Our perf and size tests run in outer loop. They do not run as part of the PR. We typically depend on target perf tests for PR validation as necessary.

In more complex scenarios where the preprocessor checks are used, does a new architecture independent target, e.g. TARGET_ARCH_NEUTRAL, make sense to introduce and use alongside the existing code paths

I think the arch-neutral config will be leaving some perf on the table. A good example is: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs . I do not think there is a good way to make the arch-neutral variant that is perf-neutral.

New TARGET_ARCH_NEUTRAL may help with readability of the code in some situations.

cc @stephentoub

@EgorBo
Copy link
Member

EgorBo commented Oct 1, 2024

The check will be turned into no-op via trimmer substitutions or by the compiler, so it is not really a runtime check

ILLink still struggles with some, e.g. #83169 (still a constant in the end, but affects inlining decisions/dll size).

Check IntPtr.Size which should evaluate to constant/no-op by either the
trimmer or the compiler.
@joncham joncham changed the title Prototype architecture independent code paths in the BCL Use architecture independent code paths in Array Oct 3, 2024
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.

Looking at some of the long lines, I have realized that we can just use goto case

src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
src/libraries/System.Private.CoreLib/src/System/Array.cs Outdated Show resolved Hide resolved
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
@jkotas jkotas merged commit 48dbc4f into dotnet:main Oct 5, 2024
146 of 148 checks passed
@am11
Copy link
Member

am11 commented Oct 6, 2024

TARGET_ARCH_NEUTRAL

Does this aim to cover only differences in bitness (i.e., 32-bit vs. 64-bit), or would it also encompass architecture types (e.g., x86/x64 vs. ARM/ARM64)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Runtime 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.

5 participants