-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
Background and motivation
.NET's struct layout system is quite extensive, but there are still some cases it does not cover that are relevant in interop scenarios at the language/runtime boundary.
In particular, we've seen requests for features like
- explicit alignment requirements
- .NET support for a mechanism like Rust's
repr(transparent)
With Swift Interop, we have a need to represent generic structs with correct Swift layouts, but we don't have a way to represent the Swift layout accurately in the general case. In the non-general case, we can represent it with an explicit StructLayout.Size
element, but we can't do that for generics.
For all of these cases, we'd like to extend the StructLayoutAttribute
to handle these cases. However, we can't extend that attribute as it's a pseudo-attribute that maps directly to metadata. Instead, we propose adding a new attribute, only usable on struct types, that's intended to encompass all future layout features, as well as the existing layout features provided by StructLayoutAttribute
.
As part of the implementation, we plan to introduce a simple source-generator to generate the corresponding StructLayoutAttribute
on the type that has our new attribute applied. To ensure that we don't accidentally make types non-blittable, we'll lower the attribute such that CharSet = CharSet.Unicode
in all cases.
In the future, we may extend this attribute to also be the "trigger" attribute for an interop source generator to generate a marshaller for a given struct type.
To limit the scope of this feature, we plan to start with only the feature required by Swift interop:
- Provide a mechanism to not include trailing padding in the .NET struct's size for struct types.
Like the LayoutKind.Sequential
support for structs in the runtime, we won't support these new layout requirements for any structs that (recursively) contain reference type fields.
API Proposal
namespace System.Runtime.InteropServices
{
public enum LayoutKind
{
Custom
}
[AttributeUsage(AttributeTargets.Struct)]
public sealed class CustomLayoutAttribute : Attribute
{
public CustomLayoutAttribute(CustomLayoutKind kind) {}
// Only valid for CustomLayoutKind.SwiftEnum
public int RequiredDiscriminatorBits { get; set; }
}
public enum CustomLayoutKind
{
Sequential, // C-style struct
Union, // C-style union
SwiftStruct, // Swift struct
SwiftEnum // Swift enumeration
}
}
namespace System.Runtime.InteropServices.Swift
{
// Represents a pointer to a Swift object (for the purposes of calculating spare bits)
public readonly unsafe struct SwiftObject
{
// Would be implemented to mask out the spare bits
public void* Value { get; }
}
// Represents a bool value in Swift (for the purposes of calculating spare bits)
public readonly unsafe struct SwiftBool
{
// Would be implemented to mask out the spare bits
// or assign while preserving spare bits
public bool Value { get; }
}
}
API Usage
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftStruct)]
public struct InnerStruct
{
public short F0; // offset 0
public sbyte F1; // offset 2
}
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftStruct)]
public struct OuterStruct
{
public ulong F0; // offset 0
public long F1; // offset 8
public InnerStruct F2; // offset 16
public sbyte F3; // offset 19
}
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftEnum, RequiredDescriminatorBits = 1)]
public struct MyOptional<T> where T : unmanaged
{
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftStruct)]
private struct Some_Payload
{
public T Value;
}
private Some_Payload Some;
// An API surface to describe the different cases and provide a C# API around accessing them, out of the scope of this API proposal
}
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftEnum, RequiredDescriminatorBits = 1)]
public struct ParsedResult
{
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftStruct)]
private struct ParsedObject_Payload
{
public SwiftPointer payload_0;
}
[StructLayout(LayoutKind.Custom)]
[CustomLayout(CustomLayoutKind.SwiftStruct)]
private struct ParsedBool_Payload
{
public SwiftBool payload_0;
}
private ParsedObject_Payload ParsedObject;
private ParsedBool_Payload SwiftBool;
// An API surface to describe the different cases and provide a C# API around accessing them, out of the scope of this API proposal
}
Alternative Designs
We could provide a set of layout primitives instead of a set of well-known layouts (the original proposal). However, we'd then have to handle all possible combinations of these primitives or block them to only the valid combinations to match our equivalent support in the current proposal.
We could go further than the original proposal and have a mechanism for specifying OS/Arch-specific layout and ABI parameter passing rules in attributes. This design would allow the runtime to be entirely out of the business of layout and ABI handling other than reading the attributes. This design has a few problems through: We'd need to consider how to provide/validate the provided options. The code-gen backends would need to respect this information (and reading from custom attributes is expensive). Generics would also be a problem in this design space.
Original API Proposal
API Proposal
namespace System.Runtime.InteropServices;
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class ImportedStructAttribute : Attribute
{
// StructLayoutAttribute matching members, excluding CharSet.
public int Pack { get; set; }
public int Size { get; set; }
public ImportedStructAttribute(LayoutKind layoutKind);
public LayoutKind LayoutKind { get; set; }
// New member for Swift layout requirements
public bool PadSizeToAlignment { get; set; }
}
API Usage
[ImportedStruct(LayoutKind.Sequential, PadSizeToAlignment = false)]
struct SwiftOptionalLike<T>
{
T value;
byte isNull;
}
Console.WriteLine(Unsafe.SizeOf<SwiftOptionalLike<int>>()); // Output: 5
Alternative Designs
We could provide a dedicated attribute for the Swift scenario.
We could have Roslyn support lowering the StructLayoutAttribute-corresponding members to metadata instead of introducing a source generator.
We could skip including the StructLayoutAttribute APIs and have the attributes represent separate concepts.
We could add new members to StructLayoutAttribute and require all compilers to recognize when new members are specified and not remove the attribute (and still lower the original members to metadata). This option is very expensive.
Risks
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
Status