using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Burst.Compiler.IL.Tests.Helpers; using NUnit.Framework; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Mathematics; using UnityBenchShared; namespace Burst.Compiler.IL.Tests { internal partial class TestStructs { [TestCompiler] public static float test_struct_func_call_by_value() { var localVar = new CustomStruct(); localVar.firstfield = 94; localVar.value = 123; return byvalue_function_helper(localVar); } [TestCompiler] public static float test_struct_func_call_by_ref() { var localVar = new CustomStruct { firstfield = 94, value = 123 }; byref_function_helper(ref localVar); return localVar.value; } [TestCompiler] public static float test_struct_func_call_instance() { var localVar = new CustomStruct2 { value = 123 }; return localVar.returnDoubleValue(); } [TestCompiler] public static float test_struct_constructor_nondefault() { var localVar = new CustomStruct2(123.0f); return localVar.value; } [TestCompiler] public static float test_struct_constructor_default() { var localVar = new CustomStruct2(); localVar.value = 1; return localVar.value; } [TestCompiler] public static float test_struct_copysemantic() { var a = new CustomStruct2 { value = 123.0f }; var b = a; b.value = 345; return b.value; } [TestCompiler] public static float test_struct_nested() { var a = new TestNestedStruct { v1 = { x = 5 } }; return a.v1.x; } [TestCompiler(1.0f)] public static float test_struct_multiple_fields(float x) { var v = new TestVector4 { x = 1.0f, y = 2.0f, z = 3.0f, w = 4.0f }; return x + v.x + v.y + v.z + v.w; } [TestCompiler] public static float test_struct_multi_assign() { var a = new MultiAssignStruct(2.0F); return a.x + a.y + a.z; } [TestCompiler] public static int test_custom_struct_return_simple() { var a = return_value_helper_simple(1, 2); return a.firstfield + a.value; } [TestCompiler] public static int test_custom_struct_return_constructor() { var a = return_value_helper_constructor(1, 2); return a.firstfield + a.value; } [TestCompiler] public static int test_struct_self_reference() { var a = new SelfReferenceStruct { Value = 1 }; return a.Value; } [TestCompiler] public static int test_struct_deep() { var deep = new DeepStruct2(); deep.value.value.SetValue(10); return deep.value.value.GetValue() + deep.value.value.value; } [TestCompiler(2)] public static int test_struct_empty(int x) { var emptyStruct = new EmptyStruct(); var result = emptyStruct.Increment(x); return result; } [TestCompiler] public static float test_struct_with_static_fields() { StructWithStaticVariables myStruct = new StructWithStaticVariables(); myStruct.myFloat = 5; myStruct = copy_struct_with_static_by_value(myStruct); mutate_struct_with_static_by_ref_value(ref myStruct); return myStruct.myFloat; } [TestCompiler(true)] [TestCompiler(false)] public static bool TestStructWithBoolAsInt(bool value) { var structWithBoolAsInt = new StructWithBoolAsInt(value); return structWithBoolAsInt; } [TestCompiler] [Ignore("IL Instruction LEAVE not yet supported")] public static int TestStructDisposable() { using (var structDisposable = new StructDisposable()) { return structDisposable.x + 1; } } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_InstructionStsfldNotSupported)] public static void TestStructWithStaticFieldWrite() { var test = new StructWithStaticField(); test.CheckWrite(); } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_LoadingFromNonReadonlyStaticFieldNotSupported)] public static void TestStructWithStaticFieldRead() { var test = new StructWithStaticField(); test.CheckRead(); } [TestCompiler] public static int TestExplicitLayoutSize() { return UnsafeUtility.SizeOf(); } [TestCompiler] public static int TestExplicitLayoutStruct() { var color = new Color() { Value = 0xAABBCCDD }; var a = color.Value + GetColorR(ref color) + GetColorG(color) + color.GetColorB() + color.A; var pair = new NumberPair() { SignedA = -13, UnsignedB = 37 }; var b = pair.SignedA - ((int)pair.UnsignedA) + pair.SignedB - ((int)pair.UnsignedB); return ((int)a) + b; } static uint GetColorR(ref Color color) { return color.R; } static uint GetColorG(Color color) { return color.G; } [TestCompiler] public static uint TestExplicitLayoutWrite() { var color = new Color() { Value = 0xAABBCCDD }; color.G = 3; ColorWriteBByRef(ref color, 7); return color.Value; } static void ColorWriteBByRef(ref Color color, byte v) { color.B = v; } [StructLayout(LayoutKind.Explicit)] private unsafe struct ExplicitLayoutStructUnaligned { [FieldOffset(0)] public int a; [FieldOffset(4)] public sbyte b; [FieldOffset(5)] public int c; [FieldOffset(9)] public fixed int d[4]; } [TestCompiler] public static unsafe int TestExplicitLayoutStructUnaligned() { var value = new ExplicitLayoutStructUnaligned { a = -2, b = -5, c = 9 }; value.d[0] = 1; value.d[1] = 2; value.d[2] = 3; value.d[3] = 4; return value.a + value.b + value.c + value.d[0] + value.d[1] + value.d[2] + value.d[3]; } [StructLayout(LayoutKind.Explicit)] public unsafe struct ExplicitLayoutStructFixedBuffer { [FieldOffset(0)] public int First; [FieldOffset(4)] public fixed int Data[128]; public struct Provider : IArgumentProvider { public object Value => new ExplicitLayoutStructFixedBuffer(3); } public ExplicitLayoutStructFixedBuffer(int x) { First = x; fixed (int* dataPtr = Data) { dataPtr[8] = x + 2; } } } #if UNITY_ANDROID || UNITY_IOS [Ignore("This test fails on mobile platforms")] #endif [TestCompiler(typeof(ExplicitLayoutStructFixedBuffer.Provider))] public static unsafe int TestExplicitLayoutStructFixedBuffer(ref ExplicitLayoutStructFixedBuffer x) { return x.First + x.Data[8]; } [StructLayout(LayoutKind.Explicit, Size = 9)] public struct ExplicitStructWithSize { [FieldOffset(0)] public int a; [FieldOffset(4)] public sbyte b; [FieldOffset(5)] public int c; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSizingExplicitStructWithSize() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 9)] public struct SequentialStructWithSize { public int a; public int b; public sbyte c; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSizingSequentialStructWithSize() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 13)] public struct SequentialStructWithSize2 { public int a; public int b; public sbyte c; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSizingSequentialStructWithSize2() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 12, Pack = 8)] private struct StructSequentialWithSizeAndPack8 { public double FieldA; public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSizingSequentialStructWithSizeAndPack8() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit, Size = 12, Pack = 8)] private struct StructExplicitWithSizeAndPack8 { [FieldOffset(0)] public double FieldA; [FieldOffset(8)] public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSizingExplicitStructWithSizeAndPack8() { return UnsafeUtility.SizeOf(); } private struct StructExplicitWithSizeAndPack8Wrapper { public byte FieldA; public StructExplicitWithSizeAndPack8 FieldB; public StructExplicitWithSizeAndPack8Wrapper(byte a, StructExplicitWithSizeAndPack8 b) { FieldA = a; FieldB = b; } } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructExplicitWithSizeAndPack8Wrapper() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 10)] private struct StructSequentialWithSizeSmallerThanActual { public double FieldA; public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSequentialWithSizeSmallerThanActual() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 12)] private struct StructSequentialWithSizeSmallerThanNatural { public double FieldA; public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructSequentialWithSizeSmallerThanNatural() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit, Size = 10)] private struct StructExplicitWithSizeSmallerThanActual { [FieldOffset(0)] public double FieldA; [FieldOffset(8)] public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructExplicitWithSizeSmallerThanActual() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit, Size = 12)] private struct StructExplicitWithSizeAndOverlappingFields { [FieldOffset(0)] public double FieldA; [FieldOffset(4)] public int FieldB; [FieldOffset(8)] public int FieldC; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructExplicitWithSizeAndOverlappingFields() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit, Size = 12)] private struct StructExplicitWithSize { [FieldOffset(0)] public double FieldA; [FieldOffset(8)] public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructExplicitWithSize() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit, Size = 17)] private struct StructExplicitWithSize17 { [FieldOffset(0)] public double FieldA; [FieldOffset(8)] public int FieldB; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructSizeNotSupported)] public static unsafe int TestStructExplicitWithSize17() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit)] public struct ExplicitStructEmpty { } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructZeroSizeNotSupported)] public static unsafe int TestStructSizingExplicitStructEmpty() { return UnsafeUtility.SizeOf(); } public struct ExplicitStructEmptyContainer { public ExplicitStructEmpty A; public int B; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructZeroSizeNotSupported)] public static unsafe int TestEmptyStructEmbeddedInStruct() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Explicit, Size = 0)] public struct ExplicitStructEmptyWithSize { } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructZeroSizeNotSupported)] public static unsafe int TestStructSizingExplicitStructEmptyWithSize() { return UnsafeUtility.SizeOf(); } public struct SequentialStructEmptyNoAttributes { } [TestCompiler] public static unsafe int TestStructSizingSequentialStructEmptyNoAttributes() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential)] public struct SequentialStructEmpty { } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructZeroSizeNotSupported)] public static unsafe int TestStructSizingSequentialStructEmpty() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 0)] public struct SequentialStructEmptyWithSize { } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructZeroSizeNotSupported)] public static unsafe int TestStructSizingSequentialStructEmptyWithSize() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Sequential, Size = 1)] public struct SequentialStructEmptyWithNonZeroSize { } [TestCompiler] public static unsafe int TestStructSizingSequentialStructEmptyWithNonZeroSize() { return UnsafeUtility.SizeOf(); } [StructLayout(LayoutKind.Auto)] public struct AutoStruct { public int a; } [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructWithAutoLayoutNotSupported)] public static unsafe int TestAutoStruct() { return UnsafeUtility.SizeOf(); } [TestCompiler] public static int TestNestedExplicitLayouts() { var nested = new NestedExplicit0() { Next = new NestedExplicit1() { Next = new NestedExplicit2() { FValue = 13.37f } } }; var a = nested.NextAsInt + nested.Next.NextAsInt + nested.Next.Next.IValue; nested.Next.Next.FValue = 0.0042f; var b = nested.NextAsInt + nested.Next.NextAsInt + nested.Next.Next.IValue; return a + b; } [TestCompiler] public static int TestNestedExplicitLayoutsSize() { return UnsafeUtility.SizeOf(); } [TestCompiler] public static uint TestBitcast() { return new FloatRepr() { Value = 13.37f }.AsUint; } [TestCompiler] public static uint TestExplicitStructFromCall() { return ReturnStruct().Value + ReturnStruct().R; } static Color ReturnStruct() { return new Color() { R = 10, G = 20, B = 30, A = 255 }; } [TestCompiler] public static unsafe uint TestExplicitLayoutStructWithFixedArray() { var x = new FixedArrayExplitLayoutStruct() { UpperUInt = 0xAABBCCDD, LowerUInt = 0xEEFF3344 }; uint sum = 0; for (int i = 0; i < 8; i++) { sum += x.Bytes[i]; if (i < 4) sum += x.Shorts[i]; } return x.UpperUInt + x.LowerUInt + sum; } [TestCompiler] public static unsafe int TestExplicitLayoutStructWithFixedArraySize() { return UnsafeUtility.SizeOf(); } public struct StructInvalid { public string WowThatStringIsNotSupported; } //private struct StructInvalidProvider : IArgumentProvider //{ // public object[] Arguments => new object[] { new StructInvalid() }; //} private static CustomStruct return_value_helper_simple(int a, int b) { CustomStruct val; val.firstfield = a; val.value = b; return val; } private static CustomStruct return_value_helper_constructor(int a, int b) { return new CustomStruct(a, b); } private static float byvalue_function_helper(CustomStruct customStruct) { return customStruct.value * 2; } private static void byref_function_helper(ref CustomStruct customStruct) { customStruct.value = customStruct.value * 2; } static StructWithStaticVariables copy_struct_with_static_by_value(StructWithStaticVariables byValue) { byValue.myFloat += 2; return byValue; } static void mutate_struct_with_static_by_ref_value(ref StructWithStaticVariables byValue) { byValue.myFloat += 2; } private struct EmptyStruct { public int Increment(int x) { return x + 1; } } private struct CustomStruct { public int firstfield; public int value; public CustomStruct(int a, int b) { firstfield = a; value = b; } } struct DeepStruct2 { #pragma warning disable 0649 public DeepStruct1 value; #pragma warning restore 0649 } struct DeepStruct1 { #pragma warning disable 0649 public DeepStruct0 value; #pragma warning restore 0649 } struct DeepStruct0 { public int value; public void SetValue(int value) { this.value = value; } public int GetValue() { return value; } } private struct CustomStruct2 { public float value; public float returnDoubleValue() { return value; } public CustomStruct2(float initialValue) { value = initialValue; } } private struct TestVector4 { public float x; public float y; public float z; public float w; } private struct StructWithBoolAsInt { private int _value; public StructWithBoolAsInt(bool value) { _value = value ? 1 : 0; } public static implicit operator bool(StructWithBoolAsInt val) { return val._value != 0; } } private struct TestNestedStruct { public TestVector4 v1; } private struct MultiAssignStruct { public float x; public float y; public float z; public MultiAssignStruct(float val) { x = y = z = val; } } private struct SelfReferenceStruct { #pragma warning disable 0649 public int Value; public unsafe SelfReferenceStruct* Left; public unsafe SelfReferenceStruct* Right; #pragma warning restore 0649 } private struct StructForSizeOf { #pragma warning disable 0649 public IntPtr Value1; public Float4 Vec1; public IntPtr Value2; public Float4 Vec2; #pragma warning disable 0649 } private struct StructWithStaticField { public static int MyField; public void CheckWrite() { MyField = 0; } public int CheckRead() { return MyField; } } private struct Float4 { #pragma warning disable 0649 public float x; public float y; public float z; public float w; #pragma warning restore 0649 } private struct StructWithStaticVariables { #pragma warning disable 0414 #pragma warning disable 0649 const float static_const_float = 9; static string static_string = "hello"; public float myFloat; public Float4 myFloat4; static float static_float_2 = 5; #pragma warning restore 0649 #pragma warning restore 0414 } struct StructDisposable : IDisposable { public int x; public void Dispose() { x++; } } [StructLayout(LayoutKind.Explicit)] private struct Color { [FieldOffset(0)] public uint Value; [FieldOffset(0)] public byte R; [FieldOffset(1)] public byte G; [FieldOffset(2)] public byte B; [FieldOffset(3)] public byte A; public byte GetColorB() { return B; } } [StructLayout(LayoutKind.Explicit)] private struct NumberPair { [FieldOffset(0)] public uint UnsignedA; [FieldOffset(0)] public int SignedA; [FieldOffset(4)] public uint UnsignedB; [FieldOffset(4)] public int SignedB; } [StructLayout(LayoutKind.Explicit)] private struct NestedExplicit0 { [FieldOffset(0)] public NestedExplicit1 Next; [FieldOffset(0)] public int NextAsInt; } [StructLayout(LayoutKind.Explicit)] private struct NestedExplicit1 { [FieldOffset(0)] public NestedExplicit2 Next; [FieldOffset(0)] public int NextAsInt; } [StructLayout(LayoutKind.Explicit)] private struct NestedExplicit2 { [FieldOffset(0)] public float FValue; [FieldOffset(0)] public int IValue; } [StructLayout(LayoutKind.Explicit)] private struct FloatRepr { [FieldOffset(0)] public float Value; [FieldOffset(0)] public uint AsUint; } [StructLayout(LayoutKind.Explicit, Size = 24)] private struct PaddedStruct { [FieldOffset(8)] public int Value; } [StructLayout(LayoutKind.Explicit)] private unsafe struct FixedArrayExplitLayoutStruct { [FieldOffset(0)] public fixed byte Bytes[8]; [FieldOffset(0)] public fixed ushort Shorts[4]; [FieldOffset(0)] public uint UpperUInt; [FieldOffset(4)] public uint LowerUInt; } [StructLayout(LayoutKind.Explicit)] public unsafe struct Chunk { [FieldOffset(0)] public Chunk* Archetype; [FieldOffset(8)] public Chunk* metaChunkEntity; [FieldOffset(16)] public int Count; } [TestCompiler] public static unsafe int TestRegressionInvalidGetElementPtrStructLayout() { Chunk* c = stackalloc Chunk[1]; c[0].Archetype = null; c[0].metaChunkEntity = null; c[0].Count = 0; return TestRegressionInvalidGetElementPtrStructLayoutInternal(0, 1, &c); } public static unsafe int TestRegressionInvalidGetElementPtrStructLayoutInternal(int index, int limit, Chunk** currentChunk) { int rValue = 0; while (index >= limit + 1) { rValue += (*currentChunk)->Count; index += 1; } return rValue; } [StructLayout(LayoutKind.Explicit)] public unsafe struct Packet { [FieldOffset(0)] public int data; [FieldOffset(0)] public fixed byte moreData[1500]; } [TestCompiler] public static unsafe int TestExplicitSizeReporting() { return sizeof(Packet); } [StructLayout(LayoutKind.Explicit)] private struct ExplicitStructPackedButWithHoles { [FieldOffset(0)] public byte A; [FieldOffset(1)] public long B; [FieldOffset(21)] public byte C; } [TestCompiler] public static int TestExplicitStructPackedButWithHolesSize() { return UnsafeUtility.SizeOf(); } [TestCompiler] public static unsafe int TestExplicitStructPackedButWithHolesOffsetC() { var value = new ExplicitStructPackedButWithHoles(); var addressStart = &value; var addressField = &value.C; return (int)((byte*)addressField - (byte*)addressStart); } private struct ExplicitStructPackedButWithHolesContainer { public ExplicitStructPackedButWithHoles A; public int B; public ExplicitStructPackedButWithHoles C; } [TestCompiler] public static int TestExplicitStructPackedButWithHolesContainerSize() { return UnsafeUtility.SizeOf(); } [TestCompiler] public static unsafe int TestExplicitStructPackedButWithHolesContainerOffsetC() { var value = new ExplicitStructPackedButWithHolesContainer(); var addressStart = &value; var addressField = &value.C; return (int)((byte*)addressField - (byte*)addressStart); } [StructLayout(LayoutKind.Explicit)] private struct ExplicitStructNotPackedWithHoles { [FieldOffset(4)] public int A; [FieldOffset(12)] public int B; } [TestCompiler] public static int TestExplicitStructNotPackedWithHolesSize() { return UnsafeUtility.SizeOf(); } [TestCompiler] public static float TestExplicitStructNested() { StructWithNestUnion b; b.Value.Min = 5.0f; return b.Value.Min; } [TestCompiler] public static float TestExplicitStructNestedAsArgument() { float Helper(StructWithNestUnion outer) { return outer.Value.Min; } return Helper(new StructWithNestUnion { Value = new UnionValue { Min = 5.0f } }); } public struct StructWithNestUnion { public UnionValue Value; } [StructLayout(LayoutKind.Explicit)] public struct UnionValue { [FieldOffset(0)] public float Min; [FieldOffset(4)] public float Max; [FieldOffset(0)] public uint Property; } #if UNITY_ANDROID || UNITY_IOS [Ignore("This test fails on mobile platforms")] #endif [TestCompiler(typeof(NetworkEndPoint.Provider), typeof(NetworkEndPoint.Provider), ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_StructByValueNotSupported)] public static bool TestABITransformIntoExplicitLayoutTransform(NetworkEndPoint a, NetworkEndPoint b) { return a.Compare(b); } [StructLayout(LayoutKind.Explicit)] public unsafe struct NetworkEndPoint { internal const int ArrayLength = 2; [FieldOffset(0)] internal fixed byte data[ArrayLength]; [FieldOffset(0)] internal ushort family; [FieldOffset(28)] internal int length; public bool Compare(NetworkEndPoint other) { if (length != other.length) return false; return true; } public class Provider : IArgumentProvider { public object Value => default(NetworkEndPoint); } } public struct SequentialStructWithPaddingAndVectorField { public byte a; public float2 b; public class Provider : IArgumentProvider { public object Value => new SequentialStructWithPaddingAndVectorField { a = 1, b = new float2(4, 5) }; } } #if UNITY_ANDROID || UNITY_IOS [Ignore("This test fails on mobile platforms")] #endif [TestCompiler(typeof(SequentialStructWithPaddingAndVectorField.Provider))] public static int TestSequentialStructWithPaddingAndVectorField(ref SequentialStructWithPaddingAndVectorField value) { return (int)value.b.x; } private static void TestSequentialStructWithPaddingAndVectorFieldRefHelper(ref SequentialStructWithPaddingAndVectorField value) { value.b.yx = value.b; value.b = value.b.yx; } #if UNITY_ANDROID || UNITY_IOS [Ignore("This test fails on mobile platforms")] #endif [TestCompiler(typeof(SequentialStructWithPaddingAndVectorField.Provider))] public static int TestSequentialStructWithPaddingAndVectorFieldRef(ref SequentialStructWithPaddingAndVectorField value) { TestSequentialStructWithPaddingAndVectorFieldRefHelper(ref value); return (int)value.b.x; } [TestCompiler] public static unsafe int TestSequentialStructWithPaddingAndVectorFieldPtr() { var vec = new float2(1, 2); var vecPtr = &vec; var value = new SequentialStructWithPaddingAndVectorField(); value.b = *vecPtr; return (int)value.b.x; } [TestCompiler] public static unsafe int TestCreatingVectorTypeFromNonVectorScalarType() { var x = (short)4; var value = new int4(x, x, x, x); return value.w; } [StructLayout(LayoutKind.Explicit)] public struct ExplicitVectors { [FieldOffset(0)] public int A; [FieldOffset(4)] public int2 B; //NB: Any Vector type is sufficient } [TestCompiler] public static unsafe int TestVectorLoadFromExplicitStruct() { var header = new ExplicitVectors { }; return header.B.x; } [TestCompiler(DataRange.Standard)] public static unsafe int TestVectorStoreToExplicitStruct(ref int2 a) { var header = new ExplicitVectors { B = a }; return header.B.x; } [TestCompiler(typeof(StructWithNonBlittableTypes), ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_TypeNotBlittableForFunctionPointer)] public static unsafe int TestStructWithNonBlittableTypes(ref StructWithNonBlittableTypes a) { var checksum = 0; checksum = (checksum * 397) ^ a.a0; checksum = (checksum * 397) ^ a.b0; checksum = (checksum * 397) ^ a.b1; checksum = (checksum * 397) ^ (a.d0 ? 10 : 0); checksum = (checksum * 397) ^ a.a1; checksum = (checksum * 397) ^ a.b1; checksum = (checksum * 397) ^ a.c1; checksum = (checksum * 397) ^ (a.d1 ? 0 : 7); checksum = (checksum * 397) ^ a.Check; return checksum; } [TestCompiler(typeof(StructWithNonBlittableTypesWithMarshalAs))] public static unsafe int TestStructWithBlittableTypesWithMarshalAs(ref StructWithNonBlittableTypesWithMarshalAs a) { var checksum = 0; checksum = (checksum * 397) ^ a.a0; checksum = (checksum * 397) ^ a.b0; checksum = (checksum * 397) ^ a.b1; checksum = (checksum * 397) ^ (a.d0 ? 10 : 0); checksum = (checksum * 397) ^ a.a1; checksum = (checksum * 397) ^ a.b1; checksum = (checksum * 397) ^ a.c1; checksum = (checksum * 397) ^ (a.d1 ? 0 : 7); checksum = (checksum * 397) ^ a.Check; return checksum; } [TestCompiler] public static int TestSizeOfStructWithBlittableTypesWithMarshalAs() { return UnsafeUtility.SizeOf(); } #if BURST_TESTS_ONLY [TestCompiler(typeof(StructWithNonBlittableTypes), ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_TypeNotBlittableForFunctionPointer)] public static int TestStructWithNonBlittableTypesOffset(ref StructWithNonBlittableTypes a) { return Unsafe.ByteOffset(ref a.a0, ref a.a1).ToInt32(); } #endif [TestCompiler(typeof(StructWithBlittableTypes))] public static unsafe int TestStructWithBlittableTypes(ref StructWithBlittableTypes a) { var checksum = 0; checksum = (checksum * 397) ^ a.a; checksum = (checksum * 397) ^ a.b; checksum = (checksum * 397) ^ a.c; checksum = (checksum * 397) ^ a.d.x; checksum = (checksum * 397) ^ a.d.y; return checksum; } [TestCompiler] public static int TestStructWithPointerDependency() { var test = new StructWithPointerDependency(); return test.DirectNoDependency.Value; } [TestCompiler] public static uint TestExplicitStructNestedFieldAccess() { var buffer = new StructWithNestUnionContainer(); return buffer.Something.Value.Property; } public struct StructWithNestUnionContainer { public StructWithNestUnion Something => new StructWithNestUnion { Value = new UnionValue { Property = 42 } }; } public struct StructWithBlittableTypes : IArgumentProvider { public StructWithBlittableTypes(int a, int b, int c, int2 d) { this.a = a; this.b = b; this.c = c; this.d = d; } public int a; public int b; public int c; public int2 d; public object Value => new StructWithBlittableTypes(1, 2, 3, new int2(4, 5)); } public struct StructWithNonBlittableTypes : IArgumentProvider { public StructWithNonBlittableTypes(byte a0, byte b0, byte c0, bool d0, byte a1, byte b1, byte c1, bool d1, int check) { this.a0 = a0; this.b0 = b0; this.c0 = c0; this.d0 = d0; this.a1 = a1; this.b1 = b1; this.c1 = c1; this.d1 = d1; this.Check = check; } public byte a0; public byte b0; public byte c0; public bool d0; public byte a1; public byte b1; public byte c1; public bool d1; public int Check; public object Value => new StructWithNonBlittableTypes(1, 2, 3, true, 5, 6, 7, false, 0x12345678); } public struct StructWithNonBlittableTypesWithMarshalAs : IArgumentProvider { public StructWithNonBlittableTypesWithMarshalAs(byte a0, byte b0, byte c0, bool d0, byte a1, byte b1, byte c1, bool d1, int check) { this.a0 = a0; this.b0 = b0; this.c0 = c0; this.d0 = d0; this.a1 = a1; this.b1 = b1; this.c1 = c1; this.d1 = d1; this.Check = check; } public byte a0; public byte b0; public byte c0; [MarshalAs(UnmanagedType.U1)] public bool d0; public byte a1; public byte b1; public byte c1; [MarshalAs(UnmanagedType.U1)] public bool d1; public int Check; public object Value => new StructWithNonBlittableTypesWithMarshalAs(1, 2, 3, true, 5, 6, 7, false, 0x12345678); } public unsafe struct StructWithPointerDependency { public StructWithNoDependency* PointerToNoDependency; public StructWithNoDependency DirectNoDependency; } public struct StructWithNoDependency { public int Value; } [StructLayout(LayoutKind.Sequential, Size = 4096)] public unsafe struct Sized4096 { public byte First; public struct Provider : IArgumentProvider { public object Value => new Sized4096(); } } [TestCompiler(typeof(Sized4096.Provider), typeof(Sized4096.Provider))] public static unsafe void TestSized4096(ref Sized4096 a, ref Sized4096 b) { a = b; } [TestCompiler(typeof(Sized4096.Provider), typeof(Sized4096.Provider))] public static unsafe void TestSized4096ManualCopy(ref Sized4096 a, ref Sized4096 b) { for (int i = 0; i < UnsafeUtility.SizeOf(); i++) { fixed (byte* aBytes = &a.First, bBytes = &b.First) { aBytes[i] = bBytes[i]; } } } [TestCompiler(typeof(Sized4096.Provider), typeof(Sized4096.Provider))] public static unsafe void TestSized4096CopyToAlloca(ref Sized4096 a, ref Sized4096 b) { Sized4096 c = b; (&c.First)[4] = 42; a = c; } [TestCompiler(typeof(Sized4096.Provider), typeof(Sized4096.Provider))] public static unsafe void TestSized4096CopyToStackAlloc0(ref Sized4096 a, ref Sized4096 b) { Sized4096* c = stackalloc Sized4096[1]; (&c->First)[4] = 42; a = *c; } [TestCompiler(typeof(Sized4096.Provider), typeof(Sized4096.Provider))] public static unsafe void TestSized4096CopyToStackAlloc1(ref Sized4096 a, ref Sized4096 b) { byte* bytes = stackalloc byte[4096]; Sized4096* c = (Sized4096*)bytes; (&c->First)[4] = 42; a = *c; } [StructLayout(LayoutKind.Sequential, Size = 4096)] public struct MultipleSized4096 { public Sized4096 a; public Sized4096 b; public struct Provider : IArgumentProvider { public object Value => new MultipleSized4096 { a = new Sized4096(), b = new Sized4096() }; } } [TestCompiler(typeof(MultipleSized4096.Provider), typeof(Sized4096.Provider))] public static void TestMultipleSized4096(ref MultipleSized4096 a, ref Sized4096 b) { a.a = b; a.a.First = 42; b = a.b; } [TestCompiler(typeof(MultipleSized4096.Provider), typeof(Sized4096.Provider), typeof(Sized4096.Provider))] public static void TestMultipleSized4096CopyToAlloca(ref MultipleSized4096 a, ref Sized4096 b, ref Sized4096 c) { MultipleSized4096 d = default; d.a = b; b = d.b; c = a.a; a = d; } public unsafe struct Fixed4096 { public fixed byte First[4096]; public struct Provider : IArgumentProvider { public object Value => new Fixed4096(); } } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestFixed4096(ref Fixed4096 a, ref Fixed4096 b) { a = b; } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestFixed4096ManualCopy(ref Fixed4096 a, ref Fixed4096 b) { for (int i = 0; i < UnsafeUtility.SizeOf(); i++) { a.First[i] = b.First[i]; } } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestFixed4096CopyToAlloca(ref Fixed4096 a, ref Fixed4096 b) { Fixed4096 c = b; c.First[4] = 42; a = c; } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestFixed4096CopyToStackAlloc0(ref Fixed4096 a, ref Fixed4096 b) { Fixed4096* c = stackalloc Fixed4096[1]; c->First[4] = 42; a = *c; } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestFixed4096CopyToStackAlloc1(ref Fixed4096 a, ref Fixed4096 b) { byte* bytes = stackalloc byte[4096]; Fixed4096* c = (Fixed4096*)bytes; c->First[4] = 42; a = *c; } public unsafe struct PointersInStruct { public byte* a; public byte* b; public struct Provider : IArgumentProvider { public object Value => new PointersInStruct { a = null, b = null }; } } [TestCompiler(typeof(PointersInStruct.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestPointersInStruct(ref PointersInStruct a, ref Fixed4096 b) { fixed (byte* ptr = b.First) { a.a = ptr; } a.b = a.a; } private static unsafe T GenericGetT(T* t) where T : unmanaged { return *t; } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestGetStructThroughGeneric(ref Fixed4096 a, ref Fixed4096 b) { fixed (void* ptr = &a) { var elem = GenericGetT((Fixed4096*) ptr); b = elem; } } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestGetStructThroughReadArrayElement(ref Fixed4096 a, ref Fixed4096 b) { fixed (void* ptr = &a) { var elem = UnsafeUtility.ReadArrayElement(ptr, 0); b = elem; } } [TestCompiler(typeof(Fixed4096.Provider), typeof(Fixed4096.Provider))] public static unsafe void TestSetStructThroughWriteArrayElement(ref Fixed4096 a, ref Fixed4096 b) { fixed (void* ptr = &a) { var elem = a; UnsafeUtility.WriteArrayElement(ptr, 0, elem); } } private struct Fixed1021 { public unsafe fixed byte Data[1021]; } [TestCompiler(typeof(Fixed4096.Provider))] public static unsafe void TestGetSetStructThroughReadWriteArrayElement(ref Fixed4096 a) { fixed (void* ptr1 = &a) { var ptr2 = (byte*) ptr1 + 1; UnsafeUtility.WriteArrayElement(ptr1, 0, UnsafeUtility.ReadArrayElement(ptr2, 0)); } } [TestCompiler(typeof(Fixed4096.Provider))] public static unsafe void TestGetSetStructThroughReadWriteArrayElementNoAlias(ref Fixed4096 a) { fixed (void* ptr1 = &a) { var ptr2 = (byte*) ptr1 + UnsafeUtility.SizeOf(); UnsafeUtility.WriteArrayElement(ptr1, 0, UnsafeUtility.ReadArrayElement(ptr2, 0)); } } [StructLayout(LayoutKind.Sequential, Size = 2)] public struct WithPadding { public byte A; public struct Provider : IArgumentProvider { public object Value => new WithPadding { A = 42 }; } } private static readonly WithPadding withPadding = new WithPadding { A = 42 }; [TestCompiler(typeof(ReturnBox))] public static unsafe byte TestWithPadding(WithPadding* o) { *o = withPadding; return withPadding.A; } [CompilerGenerated] [StructLayout(LayoutKind.Sequential)] public unsafe struct MyCompilerGeneratedButNotReally { public fixed int A[1]; } private static readonly MyCompilerGeneratedButNotReally myCompilerGeneratedButNotReally = new MyCompilerGeneratedButNotReally { }; [TestCompiler(typeof(ReturnBox))] public static unsafe int TestMyCompilerGeneratedButNotReallyStruct(MyCompilerGeneratedButNotReally* o) { *o = myCompilerGeneratedButNotReally; fixed (int* a = myCompilerGeneratedButNotReally.A) { return *a; } } public unsafe struct UninitFieldsAreZero { public fixed ushort a[3]; public fixed byte b[3]; public UninitFieldsAreZero(ushort x, ushort y, ushort z) { a[0] = x; a[1] = y; a[2] = z; } } [TestCompiler(typeof(ReturnBox))] public static unsafe void TestUninitFieldsAreZero(UninitFieldsAreZero* o) { o->a[0] = 42; o->a[1] = 42; o->a[2] = 42; o->b[0] = 42; o->b[1] = 42; o->b[2] = 42; var n = new UninitFieldsAreZero(13, 53, 4); *o = n; } #pragma warning disable 0649 private struct ExplicitSizesMatchB { public uint U; } private struct ExplicitSizesMatchC { public ulong L; public uint U; public ushort S; public byte B; } [StructLayout(LayoutKind.Explicit)] private struct ExplicitSizesMatch { [FieldOffset(0)] public int A; [FieldOffset(4)] public ExplicitSizesMatchB B; [FieldOffset(4)] public ExplicitSizesMatchC C; } #pragma warning restore 0649 [TestCompiler] public static unsafe int TestExplicitSizesMatch() { return sizeof(ExplicitSizesMatch); } #if BURST_TESTS_ONLY private struct ExplicitLayoutAndBoolStruct { [StructLayout(LayoutKind.Explicit)] public struct ExplicitLayoutStruct { [FieldOffset(0)] public byte Byte; } // Having `bool` AND a field whose type is an explicit-layout struct // causes .NET to fallback to auto-layout for the containing struct. public bool Bool; public ExplicitLayoutStruct ExplicitLayout; public long Int64; } // This test just exists to verify that .NET does indeed do the fallback // to auto-layout that we expect it does, since this is the underlying // reason for us to prevent the combination of // "bool + explicit-layout struct". [Test] [RestrictPlatform(".NET falls back to auto-layout for this struct, but Mono doesn't", Platform.Windows)] public static unsafe void TestExplicitLayoutAndBoolCausesDotNetToFallbackToAutoLayout() { var s = new ExplicitLayoutAndBoolStruct(); var offsetStart = (IntPtr)(&s); var offsetField = (IntPtr)(&s.Int64); var offset = offsetField.ToInt64() - offsetStart.ToInt64(); // We would expect the offset to the `Int64` field to be 8. // But because .NET falls back to auto-layout, // and places the `Int64` field first, // the offset is actually 0. Assert.AreEqual((long)0, offset); } [TestCompiler(EnableAutoLayoutFallbackCheck = true, ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_NonBlittableAndNonManagedSequentialStructNotSupported)] public static long TestExplicitLayoutAndBoolIsNotSupported() { return new ExplicitLayoutAndBoolStruct { Int64 = 8 }.Int64; } private struct ExplicitLayoutNestedAndBoolStruct { public struct SequentialLayoutStruct { #pragma warning disable 0649 public ExplicitLayoutStruct ExplicitLayout; #pragma warning restore 0649 } [StructLayout(LayoutKind.Explicit)] public struct ExplicitLayoutStruct { [FieldOffset(0)] public byte Byte; } #pragma warning disable 0649 public bool Bool; public SequentialLayoutStruct SequentialLayout; #pragma warning restore 0649 public long Int64; } [TestCompiler(EnableAutoLayoutFallbackCheck = true, ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_NonBlittableAndNonManagedSequentialStructNotSupported)] public static long TestExplicitLayoutNestedAndBoolIsNotSupported() { return new ExplicitLayoutNestedAndBoolStruct { Int64 = 8 }.Int64; } private struct ExplicitLayoutStructAndBoolWithMarshalAs { [StructLayout(LayoutKind.Explicit)] public struct ExplicitLayoutStruct { [FieldOffset(0)] public byte Byte; } #pragma warning disable 0649 [MarshalAs(UnmanagedType.U1)] public bool Bool; public ExplicitLayoutStruct ExplicitLayout; #pragma warning restore 0649 public long Int64; } [TestCompiler(EnableAutoLayoutFallbackCheck = true, ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_NonBlittableAndNonManagedSequentialStructNotSupported)] public static unsafe long TestExplicitLayoutAndBoolWithMarshalAsIsNotSupported() { return new ExplicitLayoutStructAndBoolWithMarshalAs { Int64 = 8 }.Int64; } #endif private struct SequentialLayoutAndBoolStruct { public struct SequentialLayoutStruct { #pragma warning disable 0649 public byte Byte; #pragma warning restore 0649 } #pragma warning disable 0649 public bool Bool; public SequentialLayoutStruct SequentialLayout; #pragma warning restore 0649 public long Int64; } [TestCompiler] public static unsafe long TestSequentialLayoutAndBoolIsSupported() { return new SequentialLayoutAndBoolStruct { Int64 = 8 }.Int64; } } }