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

Initial blittable proposal #206

Merged
merged 1 commit into from
Oct 11, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions proposals/blittable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Blittable Types

* [x] Proposed
* [ ] Prototype
* [ ] Implementation
* [ ] Specification

## Summary
[summary]: #summary

The blittable feature will give language enforcement to the class of types known as "unmanaged types" in the C# language spec. This is defined in section 18.2 as a type which is not a reference type and doesn't contain reference type fields at any level of nesting.

## Motivation
[motivation]: #motivation

The primary motivation is to make it easier to author low level interop code in C#. Blittable types are one of the core building blocks for interop code, yet abstracting around them is difficult today due to a lack of declarative language support.

This is the case even though blittable types are well defined in the language spec and many features, like pointers, can operate only on them. The language chooses to let structs implicitly opt into being blittable by virtue of their construction with no avenue for opting out of such a classification.

While attractive in small applications this is more difficult to manage across a large set of libraries authored by different developers. It means small field additions to structs can cause compile and runtime breaks in downstream consumers with no warning to the developers that made the change. This spooky action at a distance is one of the core problems with having an implicit opt in model here.

A declarative, explicit opt in model makes it easier to code in this area. Developers can be very explicit about the types they intend for interop. This gives them compiler help when mistakes are made in their application and in any libraries they consume.

The lack of compile time support also makes it difficult to abstract over blittable types. It's not possible for instance to author common helper routines using generic code:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is possible using System.Runtime.CompilerServices.Unsafe package.


``` c#
void Hash<T>(T value) where T : blittable struct
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You use blittable struct here and blittable below. We should probably be consistent with one or the other.

I personally prefer just blittable, since you can't have a blittable class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will make it consistent. My preference is still for blittable struct though because I want to allow for other future features. A class may not be blittable but what about a static delegate?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaredpar can you say "blittable type" to cover all possible blittable types?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whoisj not quite sure what you are asking there.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaredpar seemed like confusion of using blittable struct vs blittable, I was just suggesting use of the word "type" in place of "struct" to keep it vague but accurate. 'Tis all. 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaredpar, a static delegate, as currently proposed is a blittable struct 😉

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this allow the use of generic pointers? Like: void Hash<T>(T* pointer) where T : blittable struct

{
using (T* p = &value) {
...
}
}
```

Instead developers are forced to rewrite virtually the same code for all of their blittable types:

``` c#
void Hash(Point p) {
...
}

void Hash(Time t) {
...
}
```

The lack of constraints here also make it impossible to have efficient conversions between streams of data and more structured types. This is an operation that is common in networking stacks and serialization layers:

``` c#
Span<byte> Convert<T>(ref T value) where T : blittable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the constraint be blittable struct, or is this intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Should've been blittable struct.

...
}
Copy link
Member

@jcouv jcouv Jul 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From recent chat in LDM, the question came up: is ValueTuple<int, int> blittable? (I assume it should) How would we achieve that?
One more note: the ValueTuple types are implemented with [StructLayout(LayoutKind.Auto)].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think ValueTuple<int, int> should be blittable. This feature is for low-level interop. Limiting use of abstractions in low-level interop is fine. For example, generic PInvokes are not allowed and I do not think that it was ever raised as problem.

ValueTuple types are implemented with [StructLayout(LayoutKind.Auto)]

That makes them non-blittable by definition. This optimization would need to be undone for ValueTuple<int, int> to be blittable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a large number of generic types, such as ValueTuple, they are designed to be used with more than just blittable types, and there is no real way to make it 'blittable' without breaking back-compat (under the current proposal).

With the current proposal, structs will need to be explicitly marked as blittable (blittable struct). If they are missing this decoration then they will not be considered blittable.

There are already limitation around using generics with any kind of P/Invoke. However, if we had a blittable constraint, there is really no reason why this support could not be added (just like there is no reason to block taking the address of a generic type with blittable constraints on each typeparam).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

limitation around using generics with any kind of P/Invoke
no reason to block taking the address of a generic type with blittable constraints

These two are pretty different:

Taking address of generic type is simple, well-defined operation. You can do it today using System.Runtime.CompilerServices.Unsafe. It is just a C# language limitation that you cannot write it directly.

Generics in P/Invokes is not simple and well defined. It is limitation of the PInvoke marshalling baked into the runtime. PInvoke marshalling rules are pretty complex. Throwing generics into the mix would make them even more complex.

```

Such routines are advantageous because they are provably safe at compile time and allocation free. Interop authors today can not due this (even though it's at a layer where perf is critical). Instead they need to rely on allocating routines that have expensive runtime checks to verify values are correctly blittable.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"due" -> "do"


## Detailed design
[design]: #detailed-design

The language will introduce a new declaration modifier named `blittable` that can be applied to `struct` definitions.

``` c#
blittable struct Point
{
public int X;
public int Y;
}
```

The compiler will enforce that the fields of such a struct definition fit one of the following categories:

- `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, or `bool`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, Please, Please do not leave off IntPtr and UIntPtr. These types are always blittable and are one of the core interop types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was mentioned in the issue as well. It was an ommission, will add before merging.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your list contains quite a few non blittable types, e.g. decimal, char, bool. Are those mistakes or so you plan to change this?
If you plan to change this, what about guid, datetime, datetimeoffset, timespan?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decimal, Guid, DateTime, DateTimeOffset, and TimeSpan are implicitly blittable as they are structs composed of blittable types.

char and bool are in the category of "sometimes blittable", so we should be careful about including them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decimal, Guid, DateTime, DateTimeOffset, and TimeSpan are implicitly blittable as they are structs composed of blittable types.

I've waffled a bit on whether these should be special cased as implicitly blittable, or waiting for source annotations to happen. Leaning towards implicit.

char and bool are in the category of "sometimes blittable", so we should be careful about including them.

Been way down this rabbit hole in Midori. The basic problem comes down to whether you believe bilttable means ...

  1. A type which has no field, at any level of nesting, whose type is a reference type
  2. 1 + the constraint that all possible bit patterns represent a valid instance of the type

In Midori we actually separated this out into two features: primitive and blittable. All blittable are primitive but not the other way around. The distinction was interesting but IMHO wasn't worth the extra complexity.

In any case though, this feature definitely targets 1 above. It's the language manifestation of the existing spec concept of "unmanaged type".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we are explicitly stating that this is for 'unmanaged types' (that is types for which the unmanaged and managed representation is bit for bit identical) then we should not be including 'char' or 'bool', since the statement is not always true for these types.

String is technically blittable in some cases (and there is logic for pinning a string rather than copying in those instances).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tannergooding DateTime and DateTimeOffset are currently not implicitly blittable as they are structs marked with layout Auto, even though they are composed of blittable types.
I guess that would need fixing for this proposal, otherwise this might exclude quite a few user defined blittable types (especially for serialization).
Still a pitty that nothing really can be done for bool besides using a classic BlittableBool struct wrapper.

Thank you both for your detailed explanations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I missed the Auto layout attribute. It might be worth logging bugs to remove that. At least in the case of DateTime, it currently only contains a single field (a UInt64) and will have the same layout regardless. For DateTimeOffset, it contains a DateTime and a Int16, so the layout should be the same as well.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- Any `enum` type
- Any pointer type which points to a `blittable` type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't this be any pointer? It should not matter what the pointer points to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also void* should be blittable

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I withdraw my flat proposal :)

- Any user defined struct explicitly declared as `blittable`

Any type fitting the definition above, or any element in the list, is considered a valid `blittable` type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you wanted to add a section for fixed arrays as well (#78)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that but as we were discussing I'm not sure why we should limit this to blittable types, seems we can trivially expand it to any type out there. Working to validate if that's legal or not. If it's not then I'll go back and expand this proposal to do that.

I should explicitly mention in the proposal though that a fixed array of blittable types is considered a valid field type inside a blittable struct.


``` c#
struct User
{
string FirstName;
string LastName;
}

blittable struct ItemData
{
// Error: blittable struct cannot contain field of type User which is not blittable
User User;
int Id;
}
```

Note that a user defined struct must be explicitly declared as `blittable` in order to meet the requirements above. This is required even if the struct otherwise meets the requirements of `blittable`.

``` c#
struct SimplePoint
{
public int X;
public int Y;
}

blittable struct Data
{
// Error: blittable struct cannot contain field of type SimplePoint which is not blittable
SimplePoint Point;
}
```

The language will also support the ability to constrain generic type parameters to be `blittable` types.

``` C#
void M<T>(T p) where T : blittable struct
{
...
}

M<Point>(); // Ok
M<User>(); // Error: Type User does not satisfy the blittable constraint.
```

One of the primary motivations for `blittable` structs is ease of interop. As such it's imperative that such a type have it's field ordered in a sequential, or explicit, layout. An auto layout of fields makes it impossible to reliably interop the data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: second "it's" -> "its"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its correct I think :trollface:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rule 1: When you mean it is or it has, use an apostrophe. Rule 2: When you are using its as a possessive, don't use the apostrophe.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, rule of thumb I say is if it has a apostrophe, read it in your head as "it is" and see it if makes sense.

Copy link

@weitzhandler weitzhandler Jul 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jnm2 *and see it if it 😛

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple mneumonic for its/it's: Think of the apostrophe as the dot on an 'i'.

Copy link
Contributor

@jnm2 jnm2 Jan 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GaTechThomas mnemonic
(I'm well aware that I'm in a glass house re: typos 😂)


The default today is for a sequential layout so this doesn't represent a substantial change. However the compiler will make it illegal to mark such types as having an auto layout.

``` c#
// Error: A blittable struct may not be marked with an auto layout
[StructLayout(LayoutKind.Auto)]
blittable struct LayoutExample
{
...
}
```

## Drawbacks
[drawbacks]: #drawbacks

The primary drawback of this feature is that it serves a small number of developers: typically low level library authors or frameworks. Hence it's spending precious language time for a small number of developers.

Yet these frameworks are are often the basis for the majority of .NET applications out there. Hence performance / correctness wins at this level can have a riple effect on the .NET ecosystem. This makes the feature worth considering even with the limited audience.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

frameworks are are
Just a small nitpick from someone who knows basically nothing about what is said in the proposal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're at it, riple effect should be ripple effect.


There will also likely be a small transition period after this is released where core libraries move to adopt it. Types like [System.Runtime.InteropServices.ComTypes.FILETIME](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.comtypes.filetime(v=vs.110).aspx) are common in interop code. Until it is marked as `blittable` in source though, developers won't be able to depend on it in their libraries.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very important feature for Unity, that goes far beyond low level library authors. This is something many of our customers would use. In games generally we very much like the ability to pack our memory in a very controlled way.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have blittable we can start discussing things like an IFixable<T> interface where T : blittable, which would enable fixed (void* p = myIFixable) and other useful syntax candy.

## Alternatives
[alternatives]: #alternatives

There are a couple of alternatives to consider:

- The status quo: The feature is not justified on its own merits and developers continue to use the implicit opt in behavior.
- Generic constraint only: The blittable keyword is used on generic constraints only. This allows for developers to author efficient helper libraries. But the types involved lack any declarative support and hence are fragile across distributed development.


## Unresolved questions
[unresolved]: #unresolved-questions

- blittable vs. unmanaged. The F# language has a very [similar feature](https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/generics/constraints) which uses the keyword unmanaged. The blittable name comes from the use in Midori. May want to look to precedence here and use unmanaged instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"blittable type" has an existing definition in CLR interop already: https://docs.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types . The existing definition is different from your definition, e.g. char and bool are not blittable in the existing definition. It may be a good idea to call it something else to avoid confusion.

Copy link
Member

@tannergooding tannergooding Jan 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably worthwhile to keep this as blittable and remove char and bool from the definition.

That being said, bool and char are special in that they are considered "sometimes" blittable and, last I checked, the (CoreCLR) marshaller will still blit bool and char when the appropriate conditions are met (just like it will pin and directly reference a string when the right conditions are met).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing definition also have "The following complex types are also blittable types: One-dimensional arrays of blittable types". What would you do about that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are going to go with the term "unmanaged type". It is already in use in the C# spec and F#.

- Verifier: does the verifier / runtime need to be updated to understad the use of pointers to generic type parameters? Or can it simply work as is without changes.

## Design meetings

n/a