Search code examples
c#algorithmmemory-alignmentalignof

Writing an alignof(T) method in C#?


Tried implementing alignof(T) formula from this web page but it doesn't match with Unity's:

template <typename T>
struct alignof
{
  enum { s = sizeof (T), value = s ^ (s & (s - 1)) };
};

The actual result, the first sizeof/alignof come from Unity:

Struct1     : SizeOf:  1,  1, AlignOf:  1,  1, Match:   True,   True
Struct2     : SizeOf:  2,  2, AlignOf:  2,  1, Match:   True,  False
Struct3     : SizeOf:  3,  3, AlignOf:  1,  1, Match:   True,   True
Struct4     : SizeOf:  4,  4, AlignOf:  4,  1, Match:   True,  False
Struct5     : SizeOf:  4,  4, AlignOf:  4,  4, Match:   True,   True
Struct6     : SizeOf:  8,  8, AlignOf:  8,  4, Match:   True,  False
Struct7     : SizeOf: 12, 12, AlignOf:  4,  4, Match:   True,   True
Struct8     : SizeOf: 16, 16, AlignOf: 16,  4, Match:   True,  False

The implementation showing that above formula doesn't always match:

using System.Runtime.InteropServices;
using JetBrains.Annotations;

namespace Whatever.Tests;

[TestClass]
public class UnitTestTemp
{
    [UsedImplicitly]
    public required TestContext TestContext { get; set; }

    [TestMethod]
    public void TestMethod1()
    {
        PrintSizeOfAlignOf<Struct1>();
        PrintSizeOfAlignOf<Struct2>();
        PrintSizeOfAlignOf<Struct3>();
        PrintSizeOfAlignOf<Struct4>();
        PrintSizeOfAlignOf<Struct5>();
        PrintSizeOfAlignOf<Struct6>();
        PrintSizeOfAlignOf<Struct7>();
        PrintSizeOfAlignOf<Struct8>();
    }

    public void PrintSizeOfAlignOf<T>() where T : struct
    {
        var sizeOf1 = Marshal.SizeOf<T>();
        var sizeOf2 = UnsafeUnity.SizeOf<T>();

        var alignOf1 = sizeOf1 ^ (sizeOf1 & (sizeOf1 - 1));
        var alignOf2 = UnsafeUnity.AlignOf<T>();

        TestContext.WriteLine(
            $"{typeof(T).Name,-12}: " +
            $"SizeOf: {sizeOf1,2}, {sizeOf2,2}, " +
            $"AlignOf: {alignOf1,2}, {alignOf2,2}, " +
            $"Match: {sizeOf1 == sizeOf2,6}, {alignOf1 == alignOf2,6}");
    }

    public struct Struct1
    {
        public byte B1;
    }

    public struct Struct2
    {
        public byte B1, B2;
    }

    public struct Struct3
    {
        public byte B1, B2, B3;
    }

    public struct Struct4
    {
        public byte B1, B2, B3, B4;
    }

    public struct Struct5
    {
        public int B1;
    }

    public struct Struct6
    {
        public int B1, B2;
    }

    public struct Struct7
    {
        public int B1, B2, B3;
    }

    public struct Struct8
    {
        public int B1, B2, B3, B4;
    }
}

The tiny wrapper around Unity implementation, using UnityAssemblies:

using Unity.Collections.LowLevel.Unsafe;

namespace Whatever
{
    public static class UnsafeUnity
    {
        public static int AlignOf<T>() where T : struct
        {
            return UnsafeUtility.AlignOf<T>();
        }

        public static int SizeOf<T>() where T : struct
        {
            return UnsafeUtility.SizeOf<T>();
        }
    }
}

Problem:

Basically, it works for odd values but not even values.

Why I need that:

I have some SIMD-enabled code that can run on Unity but also regular .NET.

For performance concerns, I need to do aligned allocations.

While in Unity I have access to the first two methods, in regular .NET I do not.

And although NativeMemory.AlignedAlloc exists, I still need some alignof(T).

Therefore, I have to write a custom aligned memory allocator for regular .NET.

Question:

What's the right formula for writing an alignof(T) method?


Solution

  • The algorithm appears to be the largest primitive size in the type:

    public static int AlignOf<T>() where T : struct
    {
        var type = typeof(T);
    
        if (!UnsafeHelper.IsBlittable<T>())
        {
            throw new InvalidOperationException("The type is not blittable.");
        }
    
        var result = 1;
    
        var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    
        foreach (var field in fields)
        {
            var fieldType = field.FieldType;
    
            if (fieldType.IsPrimitive)
            {
                result = Math.Max(result, Marshal.SizeOf(fieldType));
            }
        }
    
        return result;
    }
    

    The results now do match with Unity's alignof implementation:

    Struct1     : SizeOf:   1,   1, AlignOf:   1,   1, Match:   True,   True
    Struct2     : SizeOf:   2,   2, AlignOf:   1,   1, Match:   True,   True
    Struct3     : SizeOf:   3,   3, AlignOf:   1,   1, Match:   True,   True
    Struct4     : SizeOf:   4,   4, AlignOf:   1,   1, Match:   True,   True
    Struct5     : SizeOf:   4,   4, AlignOf:   4,   4, Match:   True,   True
    Struct6     : SizeOf:   8,   8, AlignOf:   4,   4, Match:   True,   True
    Struct7     : SizeOf:  12,  12, AlignOf:   4,   4, Match:   True,   True
    Struct8     : SizeOf:  16,  16, AlignOf:   4,   4, Match:   True,   True
    Struct9     : SizeOf:  60,  60, AlignOf:   4,   4, Match:   True,   True
    Struct10    : SizeOf: 272, 272, AlignOf:   4,   4, Match:   True,   True
    

    However, such method should throw on non-blittable types.

    Searched and came up with a simplified mix of the links below:

    How do I check if a type fits the unmanaged constraint in C#?

    The fastest way to check if a type is blittable?

    https://aakinshin.net/posts/blittable/

    I've ignored managed arrays as they're not blittable under Unity's Burst.

    public static class UnsafeHelper
    {
        public static bool IsBlittable<T>()
        {
            return IsBlittableCache<T>.Value;
        }
    
        private static bool IsBlittable(this Type type)
        {
            var handle = default(GCHandle);
    
            try
            {
                var instance = FormatterServices.GetUninitializedObject(type);
    
                handle = GCHandle.Alloc(instance, GCHandleType.Pinned);
    
                return true;
            }
            catch
            {
                return false;
            }
            finally
            {
                if (handle.IsAllocated)
                {
                    handle.Free();
                }
            }
        }
    
        private static class IsBlittableCache<T>
        {
            public static readonly bool Value = IsBlittable(typeof(T));
        }
    }