4

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?

aybe
  • 15,516
  • 9
  • 57
  • 105

1 Answers1

0

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));
    }
}
aybe
  • 15,516
  • 9
  • 57
  • 105