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

Champion: Unmanaged constructed types (16.3, Core 3) #1744

Open
Tracked by #829
tannergooding opened this issue Jul 27, 2018 · 14 comments
Open
Tracked by #829

Champion: Unmanaged constructed types (16.3, Core 3) #1744

tannergooding opened this issue Jul 27, 2018 · 14 comments
Assignees
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion
Milestone

Comments

@tannergooding
Copy link
Member

tannergooding commented Jul 27, 2018

Summary

Provide a way to take the address of a generic struct provided it does not contain any "managed" (GC-tracked) types.

For example, the following generic type is "unmanaged":

public struct MyStruct<T> where T : unmanaged
{
    public T field;
}

Motivation

Today, users have the ability to declare structs and take the address of it (in an unsafe context) provided that it is not considered a "managed" type.

  • The compiler currently reports generic structs as "managed" types, even though they are not GC-tracked

However, there is nothing in the runtime preventing a user from taking the address of a generic struct and, in certain scenarios, it may be desirable to allow this.

One such example is the System.Runtime.Intrinsics.Vector128<T> type, which contains no GC tracked objects. The type is designed to be used in high-performance and generally unsafe scenarios, but there are certain operations (such as stackalloc, pinning, etc) which cannot be done in C# today.

Design

The C# 6 spec states that an unmanaged_type is:

isn't a reference_type or constructed type, and doesn't contain reference_type or constructed type fields at any level of nesting. In other words, an unmanaged_type is one of the following:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.
  • Any enum_type.
  • Any pointer_type.
  • Any user-defined struct_type that is not a constructed type and contains fields of unmanaged_types only.

This change would remove the restriction that unmanaged_type cannot be a constructed type. Instead, constructed types would be unmanaged if they meet the requirements of general user-defined struct types.

Drawbacks

The compiler may need to do additional validation in order to validate that a user-defined generic struct is "ok" to use.

Notes

The user will still not be able to take the address of any type, which is possible to do in IL code today.

@tannergooding
Copy link
Member Author

CC. @jaredpar, @jkotas, @davidwrighton

@tannergooding
Copy link
Member Author

Users can currently work around this using the System.Runtime.CompilerServices.Unsafe class, which allows converting a ref T to a void*, but this is less than ideal and still excludes some scenarios (such as stackalloc).

@jkotas
Copy link
Member

jkotas commented Jul 28, 2018

The user will still not be able to take the address of any type, which is possible to do in IL code today.

This is less than ideal as well. Here is an example from our own CoreLib https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Globalization/CultureData.Windows.cs#L286 :

Interop.Kernel32.EnumSystemLocalesEx(EnumAllSystemLocalesProc, flags, Unsafe.AsPointer(ref context), IntPtr.Zero)

My preference would be to allow taking address of any struct (generic and non-generic), and make taking address of managed structs produce on by default warning that one can suppress. It is fine for this to be a separate proposal if you would like.

@tannergooding
Copy link
Member Author

@jkotas, thanks for the suggestion.

I do think this (allowing you to take the address of a "managed struct") would be better as a separate proposal.

In the example you linked, it looks like it is going a step beyond even "allow taking the address of a managed struct" (which would be a struct containing a GC-tracked type) and is turning it into, effectively, "take the address of anything" (in the example, EnumLocaleData is a class directly)

The current proposal mostly just requires relaxing an existing constraint in the compiler and some minor additional checking on T. While there may be some additional questions and design-work that would need to happen for both the "take the address of a managed struct" and the "take the address of anything" scenarios.

@jkotas
Copy link
Member

jkotas commented Jul 30, 2018

"take the address of anything" (in the example, EnumLocaleData is a class directly)

That is left-over from times before we had ref locals. It should be a struct - other similar places in CoreLib are structs. I am fixing this in dotnet/coreclr#19193. I agree that "take the address of anything" would be hard to explain. "take the address of a managed struct" would be enough.

@agocke agocke changed the title Provide a way to take the address of a generic struct provided it does not contain any "managed" (GC-tracked) types. Proposal: Unmanaged constructed types Nov 9, 2018
@RikkiGibson RikkiGibson self-assigned this Nov 9, 2018
@alrz
Copy link
Member

alrz commented Nov 17, 2018

Isn't it useful to mark the type as unmanaged to ensure and intend that it's safe to take the address of it later on?

@tannergooding
Copy link
Member Author

If you have a T that you want to take the address of sure. But, there a places (such as in the framework), where we cant use the unmanaged constraint (breaking change, etc) and where you still want to be able to take the address when it can statically be determined that a given struct is "unmanaged" and where we know it will always be unmanaged.

@alrz
Copy link
Member

alrz commented Nov 17, 2018

If you have a T that you want to take the address of sure.

I didn't mean the constraint. something like unmanaged struct S { ... }, and not as a requirement, just a piece of information and guarantee when you read the code.

@tannergooding
Copy link
Member Author

I mean, as @jkotas pointed out, there isn't anything at the IL level preventing it and you can get a pointer to such a struct with workarounds today (such as S.R.CompilerServices.Unsafe, or having a ref assembly that doesn't include any fields that are reference types).

I think it might just be better to relax it entirely, but that probably requires more discussion and may be a slightly more controversial change...

@markusschaber
Copy link

markusschaber commented Nov 17, 2018

We could use this feature very nicely:

We have some structs for p/invoke where the C side includes long members - those are 64 bit on 64 bit Linux, but 32 bit on 64 bit Windows (so we cannot use Intptr for them).

Being able to use generics could help us to avoid defining all the structs twice, which is more annyoing and error-prone than just using a TLong and then instantiating the generic with int32 or int64 in some wrapper code (which we need anyways).

Of course, having types for marshalling which map directly to the native C types would be even better... :-) - on the other hand, that might require CLR level changes, and for now, our code needs to support .NET Framework 4.6 in addition to the shiny new netstandard/netcore world...

@jcouv jcouv added this to the 8.0 candidate milestone Nov 26, 2018
@jcouv jcouv changed the title Proposal: Unmanaged constructed types Champion: Unmanaged constructed types Nov 26, 2018
@joeante
Copy link

joeante commented Dec 6, 2018

Not 100% sure if this already included, but for Unity it is very important that generic pointers inside structs will also be considered unmanaged.

We would like to make sure the following code works:

struct MyStruct<T> where T : unmanaged
{
	public T* field;

	public T this[int index]
	{
	    get  { return field[index]; }
	}
}

MyStruct<MyStruct<float>> test;
float value = test[0][0];

@jaredpar
Copy link
Member

jaredpar commented Dec 6, 2018

@joeante that is fine by the spec. Pretty sure there is a test for that but if not @RikkiGibson can you add one?

@RikkiGibson
Copy link
Contributor

Yes.

alexguirre added a commit to alexguirre/RAGENativeUI that referenced this issue Apr 15, 2019
alexguirre added a commit to alexguirre/RAGENativeUI that referenced this issue Apr 15, 2019
@jcouv jcouv changed the title Champion: Unmanaged constructed types Champion: Unmanaged constructed types (16.3, Core 3) Jul 18, 2019
@IanKemp
Copy link

IanKemp commented Aug 6, 2019

This can probably be closed in favour of #1937?

@333fred 333fred added the Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification label Oct 16, 2020
@dotnet dotnet locked as resolved and limited conversation to collaborators Nov 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion
Projects
None yet
Development

No branches or pull requests