-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Champion "Native-Sized Number Types" (VS 16.8, .NET 5) #435
Comments
Proposal: dotnet/corefxlab#1471 |
|
Actually, could C# add |
As i mentioned in the other proposal, i don't see a lot of value in C# adding any keywords here. IntN, FloatN and the like are totally reasonable names to just use as is. And if you really want a all-lower name, then just add |
Do you foresee people opting to use |
@jnm2 i'm not sure what you mean? Can you give an example? I don't generally think about iteration as being related to native sized ints... so i'm not sure what the connection is. Thanks! |
@CyrusNajmabadi Just a plain |
I would really hope not :) But i'll let the CLR guys weigh in on that. |
native int is more performant than int32 for loops that iterate over memory on 64-bit platforms. int32 tend to require int32->native int casts that are unnecessary overhead. E.g. Check how some methods on |
I wonder, should nint/nuint be allowed as underlying type of enums as well? And nint/nuint consts? |
@zippec that would be a breaking change in struct layouts wouldn't it? |
@bbarry, how would that be a breaking change? Only new enums would support being subclassed from nint/nuint and the runtime already supports said functionality. The runtime also supports native sized constants (although they are actually 32-bit constants that are implicitly converted) |
I misread that comment as changing the default underlying type. |
If we add this feature, how is the compiler supposed to know if the following code is valid or not (because it would overflow nint t = (nint)int.MaxValue + (nint)1; |
@gafter , I would think the compiler could determine the constant is larger than 32-bits and spit a warning that this will result in different behavior on 32-bit vs 64-bit platform. Essentially the compiler just does all folding as the largest size (long or ulong) then explicitly downcasts to 32-bits for the store. If the downcast causes an overflow/underflow the compiler can warn/error. |
For compile-time constant folding, my instinct is to define the feature in the following way:
|
I added some additional thoughts in the GitHub issue for the original proposal. For the case of native int ( |
I don't see how that works: const nint a = unchecked((nint)uint.MaxValue + (nint)1);
const bool c = a == (nint)0; The bool |
@gafter Interesting example. Outside of situations where the bit size is known at compile time (e.g. |
@sharwell Constant folding is not currently an "optimization" - it is required by the specification to occur under certain circumstances, and under no other circumstances. It is observable without void M(nint value)
{
switch (value)
{
case (nint)0:
case unchecked((nint)uint.MaxValue + (nint)1): // allowed, or duplicate case?
break;
}
} |
@gafter, The CLI spec explicitly states that |
The specific section is (I.12.1.4 CIL instructions and numeric types):
|
I would assume that means any
The language spec would then need to determine whether or not
It might worth noting that |
@gafter If |
That is just an elegance issue, you still can compile for AnyCPU, the exposed structure just isn't as nice to use.
That is, for something which has a different offset for 32-bit vs 64-bit: struct S {
int32_t x;
union {
void* a;
intptr_t b;
};
int32_t y;
}; You can just define: public struct S
{
public int x;
public AnonymousStruct Anonymous;
public int y;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct AnonymousStruct
{
[FieldOffset(0)]
public void* a;
[FieldOffset(0)]
public nint b;
}
} You can see (and experiment with) the struct layouts for different struct types here: https://godbolt.org/z/NDsS3h |
Yes, this is about unions. While the way you propose works, it stupidifies FieldOffsetAttribute, because your solution is the same as recommending: Also, even if you classify this as an elegance issue, what's wrong with that? There are countless language features already in C# that are purely elegance: auto-generated properties, null-conditional operator, discards, local functions, switch expression, and the list goes on for long... |
Tanner's code seems very readable to me... |
That's only because of the specific example. It is easy to show that in other cases, supporting nint-sized FieldOffsets would result in simpler code. Here's a minimal counterexample: Tanner's version: public struct S
{
public nint a;
public U u;
}
[StructLayout(LayoutKind.Explicit)]
public struct U
{
[FieldOffset(0)]
public int b;
[FieldOffset(0)]
public char c;
} My proposal: [StructLayout(LayoutKind.Explicit)]
public struct S
{
[FieldOffset(0)]
public nint a;
[FieldOffset(nint.Size)]
public int b;
[FieldOffset(nint.Size)]
public char c;
} |
Absolutely nothing. It's just important to clarify whether something is truly blocking or if it is mainly a "nice to have". Function pointers, for example, are the only way to remove allocations and overhead for a good deal of interop code. It is impactful enough that dev's went and did post-compillation IL rewriting or other hacks to workaround the issue. While adding support for declaring non-constant field offsets is really a nice to have. It doesn't block anything, it just means you have to write more code to achieve the same thing and otherwise the perf, allocations, etc are all the same. Strictly speaking it also isn't correct. An anonymous struct/union is still a new struct/union and is surfaced as such by the C/C++ AST, even if the members in source appear as though they are part of the containing struct/union. Exposing it as an actual nested struct is the technically correct thing to do, even if in most (or possibly all) scenarios they result in the same ultimate layout.
At the same time, that doesn't work for more complex scenarios, such as if the struct S
{
nint x;
[FieldOffset(nint.Size)]
char y;
// How do you define field offset for this, which will be 8 or 16
nint z;
// What about here, which will be 12 or 24
char w;
} To cover all applicable scenarios, the runtime either needs to support saying this is the 32-bit field offset and this is the 64-bit or for some way to say "these members are part of union 1" and "these members are part of union 2". |
Actually, it does work and you could express that too very simply with my proposal. With default packing and your previous example: struct S
{
[FieldOffset(0)]
nint x;
[FieldOffset(nint.Size)]
char y;
[FieldOffset(2 * nint.Size)]
nint z;
[FieldOffset(3 * nint.Size)]
char w;
} Sure, if the struct with the union is complex enough, this can also get ugly. Nobody wants to see an offset specified as [FieldOffset(2*nint.size+sizeof(some-blittable-type))]. In my experience such cases are uncommon though, and the overwhelming majority of the native Windows API would be well-served without having to resolve to complex expressions in FieldOffsets. Of course, if you ask me, the whole idea of FieldOffset is just an ugly hack from the early days of C# that we have to live with today. Optimally, C# would support real unions like C, even if they'd only serve native-interop scenarios. Of course "support for native unions" is completely off-topic in the issue here for "Native-Sized Number Types", but I also don't think pushing unions even in their own issue is worth it. I'm 100% sure the C#-Team has already discussed extensively years ago whether they want unions in the language, and they obviously arrived at a "NO". So I thought supporting nint-sized FieldOffsets would be a way to fill in the most common gaps while making both sides happy. |
I do understand this would need runtime support. But also language support. Obviously here we're talking about design and general acceptance from the language side, as it couples tightly with nint-sized integers. |
Assuming this was done with some other attribute instead (which would likely be a must), you'd end up having to support encoding arbitrary expressions into the metadata to support that mechanism. This is also probably getting out of scope for this issue in particular and I'd recommend opening a new proposal (which would be required anyways) to cover this. This might also be more appropriate for the dotnet/runtime repo, as it is primarily a runtime feature. I'm not even sure it is a language feature and is instead more likely a compiler feature (in the same way that |
Ah, okay, obviously that's an implementation detail that I (as a humble user) had no idea about. I just presented my wish that I thought (purely based on the user-visible code) would integrate most easily with the least amount of changes to the current ecosystem. I see now it's not so simple. Thanks for the pointers, I'll drop the topic here and will think about if and how I could raise the issue the best to the compiler or runtime team. |
Completely honest here: the former is more readable and clearer to me. |
Considering all the feedback I received, I'm withdrawing my proposal to support nint-sized FieldOffsets. After searching around, I've found #687, which addresses all aspects of a preferred solution discussed here:
|
Is this going to include half-native int types also? An example of this is |
This includes |
The spec proposal says:
But is there a way to use them if there is a viable result at that program location for another identically named type? Example: struct nint {
public string NoIntegerHere;
}
enum MyEnum : nint
{
MyValue = 1
}
class MyClass {
const nint MyConstant = 2;
nint GetConstant (nint parameter = 3)
{
return 123;
}
} According to the spec, this wouldn't compile, because the Would it be possible to use |
No, IntPtr has the same semantics it had before.
Looking at this test, it's specifically a non-solution. (The test ensures This would be backwards of how verbatim identifiers normally work. While maybe useful as a workaround for this issue, I think you end up flipping the issue on its head and you'd break anyone using This is in line with how other contextual keywords work. So the solution is "Don't make a type called Unfortunately as far as I can find, this particular issue was never addressed in the LDM notes. |
That includes everybody using Xamarin.iOS or Xamarin.Mac... |
https://github.com/DotNetCross/NativeInts 🙄 I don't think any actually use though 😅 |
@nietras In the case of that library I'd expect the users to just stop using the library or omit the |
According to specification there are no native integer MinValue and MaxValue since they are not constant. Is there any solution for this ? |
That doesn't seem like a great reason to avoid them. They can still be calculated at runtime, but it's a bit tedious, and I can definitely see use cases for wanting to know the minimum and maximum when dealing with native interop.
|
MinValue/MaxValue were added as static properties to IntPtr/UIntPtr in dotnet/runtime#21943 |
Why they cannot be added to |
|
Summary:
nint i = 1;
andnuint i2 = 2;
Shipped in preview in 16.7p1.
/cc @KrzysztofCwalina @jaredpar
[jcouv update:] as part of this feature, we should consider what
Interlocked
overloads the BCL/runtime should provide for those new types.The text was updated successfully, but these errors were encountered: