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

Proposal: PrimitiveValueType and Generic Pointers #2209

Closed
Ultrahead opened this issue Apr 23, 2015 · 28 comments
Closed

Proposal: PrimitiveValueType and Generic Pointers #2209

Ultrahead opened this issue Apr 23, 2015 · 28 comments

Comments

@Ultrahead
Copy link

The idea here is to create a new type, say, "PrimitiveValueType" that would inherit directly from ValueType, and would not allow declaring reference-type field members, only value-type fields’ members that do not hold reference-types. For example:

public primitive Percentage
{}

This would also open the door another proposed feature: Generic Pointers. Take for instance the following operation:

public void DoSomeStuff (TPrimitive* myPrimitivePtr) 
   where TPrimitive: primitive
{}

By redefining types like int, single, double, bool, and so on so forth, so that they become specifications of PrimitiveValueType, fixed-type buferring could be enhanced so that any type defined as primitive is supported. Example:

public class MyClass
{
    public fixed MyPrimitiveType Foo[5]; 
}

For reference, this proposal is related to this discussion on codeplex: http://roslyn.codeplex.com/discussions/543883

@VSadov
Copy link
Member

VSadov commented Apr 23, 2015

This seems somewhat similar (if not a duplicate) to the unmanaged generic type constraints. #126
@stephentoub

@zahirtezcan
Copy link

This seems really good for interop purposes. Since primitive types can only contain other primitive types, fixed buffers or pointers: We can use sizeof operator on primitive types, such that size may be known in compile time. For this purpose there should be a way to tell the compiler about packing etc.

If that becomes possible, generic context we will be able to say

fixed (TPrimitive* ptr = &Foo[0])
    ptr->X = 5;

@HaloFour
Copy link

@zahirtezcan Currently you can use StructLayoutAttribute to inform the compiler about the packing (and actual size) of a structure. The runtime defaults to aligning based on the size of the largest element in the structure otherwise.

@zahirtezcan
Copy link

@HaloFour Attributes are for the run-time to determine the size and layout of the structure though. But if compiler can detect size and layout statically; we can use sizeof on primitive structures and that will be whole new level for pointer/interop/memory operations

@HaloFour
Copy link

@zahirtezcan Actually the StructLayoutAttribute never survives to the compiled binary. It exists as a "pseudo-attribute" to give the C# compiler a syntax for specifying the layout and characteristics of the struct without having to define a new syntax. The compiler is, of course, free to calculate that on its own and specify that metadata. It would have to in these cases since the size of the struct would have to be known for fixed sized buffers to be supported since the C# compiler has to account for the total size of the buffer.

@dsaf
Copy link

dsaf commented Apr 27, 2015

Could it be used for implementing units of measure? #144

@MikePopoloski
Copy link

+1

Lack of generic pointers is one of my biggest pain points with C#.

@VSadov
Copy link
Member

VSadov commented Apr 28, 2015

@MikePopoloski - about generic pointers. I am curious about particular scenarios.
Is that specifically about
-- pointers - as in generalizing byte* to primitive_struct* and use that in unsafe/pinned context,
or about
-- inner references - as in getting a managed reference to an element of T[] and pass that around like described in #118?

@Ultrahead
Copy link
Author

@VSadov: I was migrating the proposal I had submitted to codeplex. Now, for what I can read through #126 and #118, my proposal is not a duplicate since I meant the last two features proposed in my first post here -that is generic pointers and fixed-type buferring- for unsafe contexts.

@zahirtezcan: exactly!

@dsaf: I don't see why not. It would nice to have user-defined literals as in C++11, so you can do something like:

public primitive Radian
{public static Radian operator _rad;

   public static implicit operator Radian(int value)
   {
       return new Radian((value % 360) * Math.PI / 180f);
   }

   public static implicit operator Radian(float f)
   {
       // do the corresponding calculation here ...
   }
   ...
}

So when you use it like var myRadian = 45_rad; or var myRadian = 45.23f_rad;, the literal will pick the corresponding implicit operator to get the Radian;

@MikePopoloski: thanks!

@MikePopoloski
Copy link

@VSadov More of the former than the latter. Things like reading binary files from disk, writing binary blobs to the network, or manipulating native memory blocks allocated for maximum control of cache layout (for example, particle systems). I usually end up using an IL-rewriting process to let me read and write generic pointers from/into a native memory block, since the CLR supports it just fine.

If that, and the calli instruction were exposed to C# code it'd make me super happy. I'd no longer need to run the cumbersome IL rewriting build step.

@Ultrahead
Copy link
Author

@MikePopoloski: "Things like ..." +1 on that

@dsaf
Copy link

dsaf commented Apr 29, 2015

Would it lift the unpleasant constants limitation?

https://msdn.microsoft.com/en-us/library/e6w8fe1b.aspx

Constants can be numbers, Boolean values, strings, or a null reference.

I want to be able to pass e.g. a DateTime or a TimeSpan or a Vector2 to a custom attribute, but there is no way to do this at the moment.

Should it be a separate issue or the current one covers it?

@Ultrahead
Copy link
Author

Interesting question, @dsaf .

To be honest, I believe it goes beyond the scope of my suggestion since it would most likely requiere a deeper change in the compiler than adding a "primitive" type.

The thing is that only "simple" built-in types (plus strings) can be declared as constants, leaving out, for this case, user-defined primitives which are initialized at runtime. In order to remove such restriction the compiler should be capable of detecting beforehand which primitives can be deemed as "simple" types, and therefore, that can be assigned at compile time.

And that is a requirement that imvho should be part of a separate issue.

@dsaf
Copy link

dsaf commented Apr 29, 2015

I was thinking that this:

... not allow declaring reference-type field members ...

Would lead to this:

... compiler should be capable of detecting beforehand which primitives can be deemed as "simple" types ...

It's interesting that custom attribute constructors accepting non-primitive parameters are allowed to be declared but not to be used :)

@Ultrahead
Copy link
Author

At first, for a moment, I thought the same as you have, but then I realized that there would be more in a type to be considered simple by the compiler than be redefined as a primitive, or otherwise, structs like DateTime, TimeSpan and Vector2 would already be allowed to be declared as constants in the current version of .NET.

@Ultrahead
Copy link
Author

And of course, if I'm wrong, then that would be a welcome feature to have as a result of the proposed primitive type (that is, without the need of submitting a separate issue).

@HaloFour
Copy link

@dsaf

I believe that such changes would require modifications to the CLR, particularly where those constants are to be used with attributes. The limitations on what kinds of values can be encoded and deserialized for attributes are set by the runtime and are a relatively short list of very easy to serialize values, namely integral types, char, enum, bool, string, Type (by way of the full name encoded as string) or arrays of any of those types.

For the most part it seems that this primitive type proposal is compiler candy, a way to explicitly specify that a standard struct is to contain only other value types of a size known at compile-time. That would allow for fixed-size buffers using the compiler trickery currently used. Although the concept of "generic pointers" is an entirely different ballgame.

@Ultrahead
Copy link
Author

Although the concept of "generic pointers" is an entirely different ballgame.

Indeed, but having a primitive type in the language would ease such task if used as a required constraint (that is, not allowing type declared as structs for generic pointers even if they hold no reference types within). So, the only way to have generic pointers would be by the use of primitives.

@Ultrahead
Copy link
Author

This branch could really help to bring PrimitiveValueType base class to life: http://xoofx.com/blog/2015/09/27/struct-inheritance-in-csharp-with-roslyn-and-coreclr/

@HaloFour
Copy link

HaloFour commented Oct 8, 2015

@Ultrahead I don't see how that branch does anything to make this proposal possible. You still have the problem that dereferencing that pointer requires that the compiler know at compile time the exact offset in bytes of every member or element. The compiler can't know that with generic methods.

As for the branch itself, calling that "struct inheritance" seems like a bit of a stretch. A form of struct composition maybe. The behavior would probably not be what people would expect, especially the slicing for upcasting which makes downcasting impossible.

@gafter
Copy link
Member

gafter commented Oct 8, 2015

@Ultrahead @HaloFour Actually, I like the idea of struct inheritance. The problem we run into is the meaning of interface implementations. You should be able to override the implementation of a method provided by the base struct, but that is incompatible with the tearing you get when you cast to the base struct.

@Ultrahead
Copy link
Author

You still have the problem that dereferencing that pointer requires that the compiler know at compile time the exact offset in bytes of every member or element. The compiler can't know that with generic methods.

@HaloFour: At compile time, the compiler will know the size of each type of primitive. So, couldn't that task be endorsed to the CLR so that it infers it at runtime, instead?

@xoofx
Copy link
Member

xoofx commented Oct 9, 2015

We definitely need a way to express/enforce blittable structs (so that if someone add a managed field, we can detect it) and enforce this at generic constraint level, to be able in the end for example, to take a pointer from it (and pass this pointer to a native method then).

As @MikePopoloski mentioned, we are currently workaround it by IL rewriting this, just get an access to this feature which is very annoying (In SharpDX for example, it is all around used, declared in this file, and IL rewrite here, and used for example here)

In the language, it could also be just a System attribute (Blittable), and on the constraint, we could specify constraints on attributes ( where MyType has Blittable(Attribute))

@MikePopoloski
Copy link

Also note that similar things can be done using typed references: link

Of course, this abuses the internal structure of a typed reference, but it does exactly what I want in a very efficient manner.

@xoofx
Copy link
Member

xoofx commented Oct 11, 2015

neat! didn't know that _makeref was also working with generics

@nietras
Copy link

nietras commented Feb 4, 2016

I guess my proposal here is a redundant form of this one Unmanaged generic type constraint + generic pointers Note that this proposal however does not require a new type specifier.

@jamesqo
Copy link
Contributor

jamesqo commented May 9, 2016

Enthusiastically +1'ing this proposal, having generic pointers would be a very welcome addition to C# for me. A couple of things I'd do differently, though:

  • Make primitive a modifier for the struct keyword, so e.g. you would write

    public primitive struct Percentage { ... }
  • primitive is just a way for the compiler to verify that the type does not contain any reference fields, it doesn't get reflected at all in the emitted IL. This would mean we could work with existing types that don't have the modifier and use them with the primitive constraint:

    public struct Foo { int field; }
    
    DoSomething(new Foo()); // compiles fine
    
    void DoSomething<T>(T thing) where T : primitive {}

    In other words, types that don't declare primitive might be primitive, types that do are always primitive. This means we don't have to change existing code.

  • No PrimitiveValueType base class, since that would probably break things like reflection if you tried to do this

    Console.WriteLine(typeof(int).BaseType); // "System.ValueType" before, "System.PrimitiveValueType" after

@gafter
Copy link
Member

gafter commented Apr 23, 2017

Issue moved to dotnet/csharplang #492 via ZenHub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests