Skip to content

Commit

Permalink
[RGen] Add a roslyn based implementation of the GetValueTypeSize. (#2…
Browse files Browse the repository at this point in the history
…2037)

Add a roslyn based implemenation of the GetValueTypeSize method that
calulcates the size of a struct type based on the target platform.

Tests are written so that we can compare the output of the reflection
based implementation with the roslyn one.

---------

Co-authored-by: GitHub Actions Autoformatter <[email protected]>
Co-authored-by: Copilot <[email protected]>
  • Loading branch information
3 people authored Jan 24, 2025
1 parent fffd487 commit b1a1800
Show file tree
Hide file tree
Showing 3 changed files with 377 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/ObjCRuntime/Stret.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public static bool X86_64NeedStret (Type returnType, Generator generator)
// IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicFields', 'DynamicallyAccessedMemberTypes.NonPublicFields' in call to 'System.Type.GetFields(BindingFlags)'. The parameter 'type' of method 'ObjCRuntime.Stret.GetValueTypeSize(Type, List<Type>, Boolean, Object)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
[UnconditionalSuppressMessage ("", "IL2070", Justification = "Computing the size of a struct is safe, because the trimmer can't remove fields that would affect the size of a marshallable struct (it could affect marshalling behavior).")]
#endif
static int GetValueTypeSize (Type type, List<Type> fieldTypes, bool is_64_bits, Generator generator)
internal static int GetValueTypeSize (Type type, List<Type> fieldTypes, bool is_64_bits, Generator generator)
{
int size = 0;
int maxElementSize = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ public static LayoutKind GetStructLayout (this ITypeSymbol symbol)
return LayoutKind.Auto;
}

/// <summary>
/// Return all the fields that determine the size of a struct.
/// </summary>
/// <param name="symbol">The symbol whose fields to retireve.</param>
/// <returns>an array with all the none static fields of a struct.</returns>
public static IFieldSymbol [] GetStructFields (this ITypeSymbol symbol)
=> symbol.GetMembers ()
.OfType<IFieldSymbol> ()
.Where (field => !field.IsStatic)
.ToArray ();

/// <summary>
/// Returns if a type is blittable or not.
/// </summary>
Expand Down Expand Up @@ -191,9 +202,7 @@ public static bool IsBlittable (this ITypeSymbol symbol)
}

// Recursively check all fields of the struct
var instanceFields = symbol.GetMembers ()
.OfType<IFieldSymbol> ()
.Where (field => !field.IsStatic);
var instanceFields = symbol.GetStructFields ();
foreach (var member in instanceFields) {
if (!member.Type.IsBlittable ()) {
return false;
Expand All @@ -215,7 +224,6 @@ public static bool IsBlittable (this ITypeSymbol symbol)
/// <returns></returns>
public static int GetFieldOffset (this IFieldSymbol symbol)
{

var offsetAttribute = symbol.GetAttributes ()
.FirstOrDefault (attr =>
attr.AttributeClass?.ToString () == typeof (FieldOffsetAttribute).FullName);
Expand All @@ -224,6 +232,28 @@ public static int GetFieldOffset (this IFieldSymbol symbol)
? (int) offsetAttribute.ConstructorArguments [0].Value! : 0;
}

/// <summary>
/// Return the value of the MarshallAs attribute.
/// </summary>
/// <param name="symbol">The symbol under test.</param>
/// <returns>The type to use for the symbol marshaling.</returns>
public static (UnmanagedType Type, int SizeConst)? GetMarshalAs (this ISymbol symbol)
{
var marshalAsAttribute = symbol.GetAttributes ()
.FirstOrDefault (attr =>
attr.AttributeClass?.ToString () == typeof (MarshalAsAttribute).FullName);
if (marshalAsAttribute is null)
return null;
var type = (UnmanagedType) marshalAsAttribute.ConstructorArguments [0].Value!;
int sizeConst = 0;
foreach (var (name, value) in marshalAsAttribute.NamedArguments) {
if (name == "SizeConst")
sizeConst = (int) value.Value!;
}

return (type, sizeConst);
}

/// <summary>
/// Indicate whether the current symbol represents a type whose definition is nested inside the definition of
/// another symbol.
Expand All @@ -245,19 +275,128 @@ internal static bool TryGetBuiltInTypeSize (this ITypeSymbol symbol, bool is64bi
size = 0;
return false;
}

#pragma warning disable format

var symbolInfo = (
ContainingNamespace: symbol.ContainingNamespace.ToDisplayString (),
Name: symbol.Name,
SpecialType: symbol.SpecialType
);
var (currentSize, result) = symbolInfo switch { { SpecialType: SpecialType.System_Void } => (0, true), { ContainingNamespace: "ObjCRuntime", Name: "NativeHandle" } => (is64bits ? 8 : 4, true), { ContainingNamespace: "System.Runtime.InteropServices", Name: "NFloat" } => (is64bits ? 8 : 4, true), { ContainingNamespace: "System", Name: "Char" or "Boolean" or "SByte" or "Byte" } => (1, true), { ContainingNamespace: "System", Name: "Int16" or "UInt16" } => (2, true), { ContainingNamespace: "System", Name: "Single" or "Int32" or "UInt32" } => (4, true), { ContainingNamespace: "System", Name: "Double" or "Int64" or "UInt64" } => (8, true), { ContainingNamespace: "System", Name: "IntPtr" or "UIntPtr" or "nuint" or "nint" } => (is64bits ? 8 : 4, true),

var (currentSize, result) = symbolInfo switch {
{ SpecialType: SpecialType.System_Void } => (0, true),
{ ContainingNamespace: "ObjCRuntime", Name: "NativeHandle" } => (is64bits ? 8 : 4, true),
{ ContainingNamespace: "System.Runtime.InteropServices", Name: "NFloat" } => (is64bits ? 8 : 4, true),
{ ContainingNamespace: "System", Name: "Char" or "Boolean" or "SByte" or "Byte" } => (1, true),
{ ContainingNamespace: "System", Name: "Int16" or "UInt16" } => (2, true),
{ ContainingNamespace: "System", Name: "Single" or "Int32" or "UInt32" } => (4, true),
{ ContainingNamespace: "System", Name: "Double" or "Int64" or "UInt64" } => (8, true),
{ ContainingNamespace: "System", Name: "IntPtr" or "UIntPtr" or "nuint" or "nint" } => (is64bits ? 8 : 4, true),
_ => (0, false)
};
#pragma warning restore format
size = currentSize;
return result;
}

static int AlignAndAdd (int size, int add, ref int maxElementSize)
{
maxElementSize = Math.Max (maxElementSize, add);
if (size % add != 0)
size += add - size % add;
return size + add;
}


static void GetValueTypeSize (this ITypeSymbol originalSymbol, ITypeSymbol type, List<ITypeSymbol> fieldSymbols, bool is64Bits, ref int size,
ref int maxElementSize)
{
// FIXME:
// SIMD types are not handled correctly here (they need 16-bit alignment).
// However we don't annotate those types in any way currently, so first we'd need to
// add the proper attributes so that the generator can distinguish those types from other types.

if (type.TryGetBuiltInTypeSize (is64Bits, out var typeSize) && typeSize > 0) {
fieldSymbols.Add (type);
size = AlignAndAdd (size, typeSize, ref maxElementSize);
return;
}

// composite struct
foreach (var field in type.GetStructFields ()) {
var marshalAs = field.GetMarshalAs ();
if (marshalAs is null) {
GetValueTypeSize (originalSymbol, field.Type, fieldSymbols, is64Bits, ref size, ref maxElementSize);
continue;
}
var (marshalAsType, sizeConst) = marshalAs.Value;
var multiplier = 1;
switch (marshalAsType) {
case UnmanagedType.ByValArray:
var types = new List<ITypeSymbol> ();
var arrayTypeSymbol = (field as IArrayTypeSymbol)!;
GetValueTypeSize (originalSymbol, arrayTypeSymbol.ElementType, types, is64Bits, ref typeSize, ref maxElementSize);
multiplier = sizeConst;
break;
case UnmanagedType.U1:
case UnmanagedType.I1:
typeSize = 1;
break;
case UnmanagedType.U2:
case UnmanagedType.I2:
typeSize = 2;
break;
case UnmanagedType.U4:
case UnmanagedType.I4:
case UnmanagedType.R4:
typeSize = 4;
break;
case UnmanagedType.U8:
case UnmanagedType.I8:
case UnmanagedType.R8:
typeSize = 8;
break;
default:
throw new Exception ($"Unhandled MarshalAs attribute: {marshalAs.Value} on field {field.ToDisplayString ()}");
}
fieldSymbols.Add (field.Type);
size = AlignAndAdd (size, typeSize, ref maxElementSize);
size += (multiplier - 1) * size;
}

}

/// <summary>
/// Return the size of a blittable structure that can be used in a PInvoke
/// </summary>
/// <param name="type">The type symbol whose size we want to get.</param>
/// <param name="fieldTypes">The fileds of the struct.</param>
/// <param name="is64Bits">If the calculation is for a 64b machine.</param>
/// <returns></returns>
internal static int GetValueTypeSize (this ITypeSymbol type, List<ITypeSymbol> fieldTypes, bool is64Bits)
{
int size = 0;
int maxElementSize = 1;

if (type.GetStructLayout () == LayoutKind.Explicit) {
// Find the maximum of "field size + field offset" for each field.
foreach (var field in type.GetStructFields ()) {
var fieldOffset = field.GetFieldOffset ();
var elementSize = 0;
GetValueTypeSize (type, field.Type, fieldTypes, is64Bits, ref elementSize, ref maxElementSize);
size = Math.Max (size, elementSize + fieldOffset);
}
} else {
GetValueTypeSize (type, type, fieldTypes, is64Bits, ref size, ref maxElementSize);
}

if (size % maxElementSize != 0)
size += (maxElementSize - size % maxElementSize);

return size;
}

/// <summary>
/// Returns the parents and all the implemented interfaces (including those of the parents).
/// </summary>
Expand Down
Loading

0 comments on commit b1a1800

Please sign in to comment.