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

Support natural size data types in the CLR #4233

Closed
migueldeicaza opened this issue May 8, 2015 · 25 comments
Closed

Support natural size data types in the CLR #4233

migueldeicaza opened this issue May 8, 2015 · 25 comments
Milestone

Comments

@migueldeicaza
Copy link
Contributor

There are cases (in particular on OSX) where native APIs are defined in terms of the natural size of the platform. This means that APIs use 32-bit integers on 32-bit platforms and 64-bit integers in 64-bit platforms.

Mono uses a poor man's approach to solve this problem, and we introduce the structures System.nint, System.nuint and System.nfloat that stand for native integer, native unsigned integer, and native float. These data types will be 32 bits or 64 bits depending on the platform.

The usual set of operators on ints, uints and floats are defined on those structures.

The Mono runtime then has special support to turn sequences that would invoke those operators into the native representation for it.

This means that code that would pass two nint structures, and call the addition operator on it are turned into a regular integer addition operation with no performance loss.

This is the poor man's way of introducing a new data type that is not part of the CIL spec.

This is what we published on the subject:

http://developer.xamarin.com/guides/cross-platform/macios/nativetypes/

@jkotas
Copy link
Member

jkotas commented May 8, 2015

This sounds like a good candidate for independent (Apple-specific?) contract and NuGet package. It should not need to be wired into CoreCLR runtime.

RyuJIT is pretty good about generating code for struct wrappers. Struct wrappers are frequently used for performance critical paths. The struct promotion optimization was specifically designed to optimize them.

I have done a quick experiment:

public struct nint
{
    long _value;

    public nint(long value)
    {
        _value = value;
    }

    public static implicit operator nint(int value)
    {
        return new nint(value);
    }

    static public nint operator+(nint a, nint b)
    {
        return new nint(a._value + b._value);
    }
}

...
static nint PlusOne(nint x)
{
    return x + 1;
}
...

Code:

ConsoleApplication2!ConsoleApplication2.Program.PlusOne(ConsoleApplication2.nint):
00007ff9`761404b0 488d4101        lea     rax,[rcx+1]
00007ff9`761404b4 c3              ret

About as good code as it can be.

If there are specific natural data type patterns that are hard to optimize in general way, we can consider treating them in the JIT as intrinsic. Same as SIMD Vector types are treated today - vector types live in independent contract and NuGet package as well. I cannot think about a good example where such treatment would be required.

@sharwell
Copy link
Member

sharwell commented May 8, 2015

How is this different from the native int type defined in ECMA-335 §I.8.2.2 and §I.12.1.1, and mentioned in the operand type table in §III.1.5?

@jcdickinson
Copy link
Contributor

@sharwell agreed, nint and nuint are redundant. However nfloat would remain a problem.

One way to do this, today, would be to compile both multiple assemblies with the CPU architecture flag set (with a Single or Double in your struct depending on the arch). System.AppDomain.CurrentDomain.AssemblyResolve could then be used to load the correct one into the process when it is requested.

What would be awesome is if you could host a GAC (LAC?) alongside your bin, and not have to monkey about with AssemblyResolve.

@cmckinsey
Copy link
Contributor

Just to add to Jan's point, singleton structs are very common already in .NET and so we strive to make them as efficient as scalar code as we can. There are certainly cases today where we fall short but we'd take such cases as an opportunity to do better.

@OtherCrashOverride
Copy link

This is not an Apple specific issue. Anywhere P/Invoke encounters a type such as 'sizet', the API differs on 32bit vs 64bit. System.IntPtr and SystemUIntPtr can be used in these places as its 4 bytes on 32bit and 8 bytes on 64bit and has a size property for runtime 'discovery'. The issue with it is that you must cast to perform arithmetic.

struct NativeInteger
{
IntPtr value;

... explicit conversions, etc ...
}

Is probably an end-user solution to it.

With regards to floating point, ECMA 335 I.8.2.2 explicitly defines System.Single to be 32bit and System.Double to be 64bit. There is no type that changes based on platform such as IntPtr. However, I have never encountered a need for such

@damageboy
Copy link
Contributor

Isn't this primarily a semantic issue, more related to language design than a codegen issue?

native machine size types are already supported by the clr as @sharwell, just not really exposed nicely in the upper layers like c#.

The main issue is that IntPtr / UIntPtr, while containing the right size, are really not full blown types, in the sense that substracting two (U)IntPtr and comparing them is not supported for the most part...

Sure, I could write nint and nuint and implement every operator in existence to make them feel native. but this should really be part of the language, via whatever means the language designers feel are necessary.

When it actually does come to what IL to emit, there could be a discussion about structs vs. native int, but given that the native types DO exist, it would be weird not to use them...

@migueldeicaza Isn't this more of a language feature given that the CLR / MSIL already supports all that you requested for?

Shouldn't this issue be actually closed (here) and opened up in Roslyn?

@OtherCrashOverride
Copy link

The actual case where there is a 'native int' difference such as pointers on 32bit vs 64bit is covered already by System.IntPtr, System.UIntPtr. There does not really seem to be a case where there is actually a 'native float'. On Apple systems, there is a difference from the API calls on OSX versus iOS, but those are not platform specific: both OSX and iOS have System.Single and System.Double native types, however, a 32bit iOS does not have 64bit 'native integer type' where as a 64bit iOS does. Additionally, APIs (such as OpenGL) outside of the OS will consistently use System.Single or System.Double.

This means the latter case of a 'native float' is not actually a technical issue as much as it is a convenience issue. This can be solved by adding an abstracted type to the binding (managed wrapper) rather than the language. The concern seems to be overhead in the resulting JIT code. As illustrated earlier in this thread, the new JIT can deal with the abstraction.

Since its possible for the bindings to conform to the Common Language Specification (or whatever its called today), its not an issue for Roslyn since any .Net language (Visual Basic, F#, Iron Python, etc) may consume the binding.

In conclusion, if there were supported platforms where System.Single is native but not System.Double, it would be the same need as System.Int32 versus System.Int64 (solved by System.IntPtr). However, as of today, all platforms including OSX and iOS offer System.Double as a native type. That being said, this issue is probably best solved by an abstracted System.ValueType (struct) in the API binding (managed wrapper).

@migueldeicaza
Copy link
Contributor Author

As it was already explained before, there are various issues with the proposals.

IntPtr/UIntPtr do not define all math operators. You could work around them by having your own version of those, and hoping that the JIT will do the right thing.

There is no equivalent for floating point.

Now, I understand the desire to not make changes on something like .NET desktop, where the pain is high. But I thought the point of .NET core is to actually address fundamental problems, and this is one of them.

@sharwell
Copy link
Member

@migueldeicaza My initial recommendation is that you split this request into two issues.

  1. Update IntPtr and UIntPtr to support the same set of operators the CLR supports on the underlying native int type. I think you could make a strong case for this, and I think it has a higher likelihood of being accepted. I do anticipate some arguments against this, so consider alternatives like a NativeMath class which provides these operators as separate methods implemented as JIT intrinsics - or perhaps even as directly recognized by the C# compiler.
  2. A separate issue for a native-size floating point data type. The merits of this issue as well as the target audience is likely to differ somewhat from the previous issue.

@kingces95
Copy link
Contributor

@jkotas could you elaborate on the types of structs the struct promotion optimization targets? For example, how many fields and of what type before the optimization kicks-in or turns off?

@jkotas
Copy link
Member

jkotas commented May 11, 2016

The struct has to have no more than 4 non-overlapping fields, and the total size of the struct has to be no more than 32 bytes.

The exact algorithm is in https://github.com/dotnet/coreclr/blob/3593c4f00a93054d1507a621c6b11f8b45a9c300/src/jit/lclvars.cpp#L1379

@jkotas
Copy link
Member

jkotas commented May 11, 2016

@CarolEidt is working on a first class structs support in the optimizer - that may relax some of these limitations.

@CarolEidt
Copy link
Contributor

With regard to "first class structs" support, the primary objectives are 1) to ensure that structs are treated as close to primitive types as possible when it comes to value numbering, CSE, constant propagation and copy propagation, and 2) to support full-enregistration of structs that fit into register(s) and whose fields are not referenced. The design for this is here: https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/first-class-structs.md
It is not an immediate objective to modify the struct promotion conditions (except to relax some of the constraints with regard to structs that are passed and returned). However, I'm open to revisiting that as well, once we've got the other work items completed.

@kingces95
Copy link
Contributor

kingces95 commented Jun 2, 2016

@jkotas Would the operator+ be inlined in a R2R scenario? From the spec:

As an example, consider the issue of inlining of method bodies. If module A would inline a method from Module B, that would break our desired versioning property because now if that method in module B changes, there is code in Module A that would need to be updated (which we do not wish to do). Thus inlining is illegal across modules. Inlining within a module, however, is still perfectly fine.

So if PlusOne and operator+ are in different versioning bubbles would that prevent RyuJIT from inlining? If so, will guidance be that a versioning bubble should include all libraries exposing structs whose methods should be inlined?

@jkotas
Copy link
Member

jkotas commented Jun 2, 2016

R2R supports [NonVersionable] attribute that allows methods that are set in stone and not going to ever change to be inlined across version boundaries. Operators on natural sized int would be a perfect candidate for it.

This attribute is used in on IntPtr/UIntPtr operators today - https://github.com/dotnet/coreclr/blob/775003a4c72f0acc37eab84628fcef541533ba4e/src/mscorlib/src/System/UIntPtr.cs#L132

@fanoI
Copy link

fanoI commented Mar 15, 2017

BigInteger could benefit to have _sign and _bits defined as nuint (UIntPtr) in this way more numeric bits could be expressed in the array in 64 bit processors and values in the long range could be expressed using only _sign.

@jkotas
Copy link
Member

jkotas commented Apr 17, 2017

Design proposal: dotnet/corefxlab#1471

@GSPP
Copy link

GSPP commented May 24, 2017

This proposal needs to explain why we need more than just nicer IntPtr support at the C# level. It was already stated that IntPtr at the CLR level is fully sufficient (since it is redundant to nint as explained).

@GrabYourPitchforks
Copy link
Member

@tannergooding Is this now covered by the other work taking place in C# vNext? Is there anything else that needs to be done for this issue before we close it out?

@msftgits msftgits transferred this issue from dotnet/coreclr Jan 30, 2020
@msftgits msftgits added this to the Future milestone Jan 30, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 26, 2020
@benaadams
Copy link
Member

This proposal needs to explain why we need more than just nicer IntPtr support at the C# level.

There are other .NET languages.

@tannergooding
Copy link
Member

@benaadams, could you elaborate?

Other languages (such as F#) already support IntPtr/UIntPtr with operators since it is the framework type defined to match the runtime native int type (which itself has the direct IL support). C# is just exposing the new nint/nuint types to be partially erased wrappers over IntPtr/UIntPtr so that the operators can be exposed in a non breaking way (that is, you will write nint but it will emit as [AttributeIdentifyingAsNativeInt] IntPtr in metadata: dotnet/csharplang#2833).

nfloat is a new concept (to the runtime, framework, and language) but is realistically macOS/iOS specific. Apple has also been dropping 32-bit support entirely and there is really only legacy and a small selection of other devices (such as the watch, iirc) where this would be relevant. This leads me to believe that nfloat would be best supported by a user (or possibly framework) defined type that is float or double depending on the target platform bitness.

@benaadams
Copy link
Member

So VB will be able to do use apis that expose nint (as they will be IntPtr); however the api changes for it don't expose comparison operators for IntPtr #21943; so you couldn't use it as a loop iterator value without doing i.CompareTo((IntPtr)upperbound) = 0

@tannergooding
Copy link
Member

tannergooding commented Mar 2, 2020

however the api changes for it don't expose comparison operators

Right, IntPtr is functionally the same as Int32 (they are both runtime defined primitive types) and so it shouldn't directly expose user defined operators (and it minimally doing so in .NET 4 is one reason why we needed to go down the partial erasure route). Languages will need to treat it as a primitive type and emit the right IL instructions if they want operator support to be available.

@KathleenDollard or @jaredpar might be able to comment on if there are any plans for VB in particular.

@jaredpar
Copy link
Member

jaredpar commented Mar 2, 2020

might be able to comment on if there are any plans for VB in particular.

There are not for nint

@ericstj ericstj removed the untriaged New issue has not been triaged by the area owner label Jun 25, 2020
@jkotas
Copy link
Member

jkotas commented Nov 16, 2020

The bulk of this is covered by nint/nuint added for C# 9.

If we still need nint/nuint/nfloat for Xamarin.iOS compatibility, it would be best to create a new issue for it.

@jkotas jkotas closed this as completed Nov 16, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Jan 6, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests