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

Make explicit struct layout compatible with generics. #43486

Open
Horusiath opened this issue Jan 5, 2020 · 12 comments
Open

Make explicit struct layout compatible with generics. #43486

Horusiath opened this issue Jan 5, 2020 · 12 comments

Comments

@Horusiath
Copy link

Right now .NET doesn't allow to combine generics with structures and explicit layout, eg.:

[StructLayout(LayoutKind.Explicit)]
struct Result<TSuccess, TError>
{
    [FieldOffset(0)] private byte tag;
    [FieldOffset(1)] private TSuccess success;
    [FieldOffset(1)] private TError error;
}
// System.TypeLoadException: Could not load type 'Result`2' from assembly '' 
// because generic types cannot have explicit layout.

However pattern like this - a.k.a tagged union - is quite popular and often used in other languages, including Rust, OCaml, Haskell or even C.

While this can be done in other ways, but each of them fails in its unique ways:

  1. Using classes with inheritance, which can produce a lot of garbage and is a no go in many performance-critical paths.
  2. Using sequential layout, which becomes infeasible pretty much after we got several fields, as we are bloating the stack with data that must be mem-copied between function calls but it's mostly unused.

Anticipating the question how is runtime supposed to determine size of such generic struct, it's the same way as in the languages mentioned above: a dynamically sized, but the size is known to be a maximum possible size required to store the data.

@eiriktsarpalis
Copy link
Member

I believe that the restriction stems from the fact that the CLR does not permit overlap of struct and reference type fields, regardless of generics. In your example, this would be admissible if, for example, TSuccess were specialized to a reference type and TError was a struct.

@eiriktsarpalis
Copy link
Member

but the size is known to be a maximum possible size required to store the data.

I may have misunderstood what you're saying here, but the size of a generic struct is unbounded at runtime.

@Horusiath
Copy link
Author

I believe that the restriction stems from the fact that the CLR does not permit overlap of struct and reference type fields

I can imagine that this reason might caused by difficulties from GC perspective (which at least in theory could be solved by conservative GC approach), but it would be great to hear about other challenges, if there are any.

I may have misunderstood what you're saying here, but the size of a generic struct is unbounded at runtime.

I think you've meant at compile time. Unless I misunderstood something, at some point it must be known to the runtime (since we need to allocate a frame on stack of certain size for any struct).

@terrajobst terrajobst transferred this issue from dotnet/designs Oct 16, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-Meta untriaged New issue has not been triaged by the area owner labels Oct 16, 2020
@mangod9 mangod9 removed the untriaged New issue has not been triaged by the area owner label Oct 16, 2020
@mangod9 mangod9 added this to the Future milestone Oct 16, 2020
@Sergio0694
Copy link
Contributor

Would it be possible to lift this restriction when all types involved are blittable at least?
This would be pretty handy when porting code from C++ and dealing with raw bindings types.
In this scenario there shouldn't be as much work necessary on the runtime side to make this possible since the GC wouldn't be involved at all, right? I can open a separate issue for that in case this one was specifically about broad support for this 🙂

@tannergooding
Copy link
Member

This might be similar to the change I made to support marshalling blittable generics: #103

The runtime side changes ended up being small (~75 lines, which includes comments and newlines), while I added some significant test coverage (~10k lines).
@jkotas and @AaronRobinsonMSFT were the primary contacts for that and would likely know if they would be any issues with the VM or marshaling side of things.

@john-h-k
Copy link
Contributor

I have got this working locally with a ~4 line change and it so far seems to be working with everything (throws when the reified layout is invalid). The check was in MethodTableBuilder::GatherGenericsInfo, but it was redundant as it seems the entire type layout is checked later in MethodTableBuilder::BuildMethodTableThrowing, so I simply removed the check

@RomanZhu
Copy link

Any update on that?
It's blocking my requirement to have the same guaranteed memory layout on all machines right now

@timcassell
Copy link

timcassell commented Mar 19, 2023

generic types cannot have explicit layout is overly restrictive. I think the runtime can verify that blittable types do not overlap with reference types without that restriction (doesn't it already?). I can't even do this, which should be perfectly valid:

struct PromiseMethodBuilder<T>
{
    [StructLayout(LayoutKind.Explicit)]
    private struct RefWrapper
    {
        // Both types are reference types, so it should be fine.
        // But the runtime complains because it's using generics.
        [FieldOffset(0)]
        internal AsyncPromiseRef<T> _async;
        [FieldOffset(0)]
        internal PromiseRef<T> _task;
    }

    private RefWrapper _ref;
    private T _result;
}

@2A5F
Copy link

2A5F commented Nov 23, 2023

Both cases should be allowed

[StructLayout(LayoutKind.Explicit)]
public struct Foo<T> where T : unmanaged
{
    [FieldOffset(0)]
    public T Value;
}

[StructLayout(LayoutKind.Explicit)]
public struct Bar<T> where T : class
{
    [FieldOffset(0)]
    public T Value;
}

@kevingosse
Copy link
Contributor

I wonder if it would be possible to give some traction to this? It would really help for some native interop scenarios, and it could be constrained to where T : unmanaged is there are any concern.

@fitdev
Copy link

fitdev commented Jul 10, 2024

Really need this feature as well.

@fitdev
Copy link

fitdev commented Nov 22, 2024

Perhaps it can also be extended to any generic type T (even without unmanaged constraint) so long as struct does NOT contain any T-typed instance fields:

[StructLayout(LayoutKind.Explicit)]
struct Custom<T> {
  static readonly T DefaultT = default(T);

  [FieldOffset(0)]
  int A;

  [FieldOffset(4)]
  int B;

}

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