using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityBenchShared; using static Unity.Burst.CompilerServices.Aliasing; namespace Burst.Compiler.IL.Tests { internal partial class Aliasing { public unsafe struct NoAliasField { [NoAlias] public int* ptr1; [NoAlias] public int* ptr2; public void Compare(ref NoAliasField other) { // Check that we can definitely alias with another struct of the same type as us. ExpectAliased(in this, in other); } public void Compare(ref ContainerOfManyNoAliasFields other) { // Check that we can definitely alias with another struct which contains the same type as ourself. ExpectAliased(in this, in other); } public class Provider : IArgumentProvider { public object Value => new NoAliasField { ptr1 = null, ptr2 = null }; } } public unsafe struct ContainerOfManyNoAliasFields { public NoAliasField s0; public NoAliasField s1; [NoAlias] public NoAliasField s2; [NoAlias] public NoAliasField s3; public class Provider : IArgumentProvider { public object Value => new ContainerOfManyNoAliasFields { s0 = new NoAliasField { ptr1 = null, ptr2 = null }, s1 = new NoAliasField { ptr1 = null, ptr2 = null }, s2 = new NoAliasField { ptr1 = null, ptr2 = null }, s3 = new NoAliasField { ptr1 = null, ptr2 = null } }; } } [StructLayout(LayoutKind.Explicit)] public struct Union { [FieldOffset(0)] public ulong a; [FieldOffset(1)] public int b; [FieldOffset(5)] public float c; public class Provider : IArgumentProvider { public object Value => new Union { a = 4242424242424242, b = 13131313, c = 42.0f }; } } public unsafe struct LinkedList { public LinkedList* next; public class Provider : IArgumentProvider { public object Value => new LinkedList { next = null }; } } [NoAlias] public unsafe struct NoAliasWithContentsStruct { public void* ptr0; public void* ptr1; public class Provider : IArgumentProvider { public object Value => new NoAliasWithContentsStruct { ptr0 = null, ptr1 = null }; } } [NoAlias] public unsafe struct DoesAliasWithSubStructPointersStruct : IDisposable { public NoAliasWithContentsStruct* s; public void* ptr; public class Provider : IArgumentProvider { public object Value { get { var noAliasSubStruct = (NoAliasWithContentsStruct*)UnsafeUtility.Malloc(UnsafeUtility.SizeOf(), UnsafeUtility.AlignOf(), Allocator.Temp); noAliasSubStruct->ptr0 = null; noAliasSubStruct->ptr1 = null; var s = new DoesAliasWithSubStructPointersStruct { s = noAliasSubStruct, ptr = null }; return s; } } } public void Dispose() { UnsafeUtility.Free(s, Allocator.Temp); } } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasFieldWithItself(ref NoAliasField s) { // Check that they correctly alias with themselves. ExpectAliased(s.ptr1, s.ptr1); ExpectAliased(s.ptr2, s.ptr2); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasFieldWithAnotherPointer(ref NoAliasField s) { // Check that they do not alias each other because of the [NoAlias] on the ptr1 field above. ExpectNotAliased(s.ptr1, s.ptr2); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasFieldWithNull(ref NoAliasField s) { // Check that comparing a pointer with null is no alias. ExpectNotAliased(s.ptr1, null); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckAliasFieldWithNull(ref NoAliasField s) { // Check that comparing a pointer with null is no alias. ExpectNotAliased(s.ptr2, null); } [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe void NoAliasInfoSubFunctionAlias(int* a, int* b) { ExpectAliased(a, b); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasFieldSubFunctionAlias(ref NoAliasField s) { NoAliasInfoSubFunctionAlias(s.ptr1, s.ptr1); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckCompareWithItself(ref NoAliasField s) { s.Compare(ref s); } [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe void AliasInfoSubFunctionNoAlias([NoAlias] int* a, int* b) { ExpectNotAliased(a, b); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasFieldSubFunctionWithNoAliasParameter(ref NoAliasField s) { AliasInfoSubFunctionNoAlias(s.ptr1, s.ptr1); } [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe void AliasInfoSubFunctionTwoSameTypedStructs(ref NoAliasField s0, ref NoAliasField s1) { // Check that they do not alias within their own structs. ExpectNotAliased(s0.ptr1, s0.ptr2); ExpectNotAliased(s1.ptr1, s1.ptr2); // But that they do alias across structs. ExpectAliased(s0.ptr1, s1.ptr1); ExpectAliased(s0.ptr1, s1.ptr2); ExpectAliased(s0.ptr2, s1.ptr1); ExpectAliased(s0.ptr2, s1.ptr2); } [TestCompiler(typeof(NoAliasField.Provider), typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasFieldAcrossTwoSameTypedStructs(ref NoAliasField s0, ref NoAliasField s1) { AliasInfoSubFunctionTwoSameTypedStructs(ref s0, ref s1); } [TestCompiler(4, 13)] public static void CheckNoAliasRefs([NoAlias] ref int a, ref int b) { ExpectAliased(in a, in a); ExpectAliased(in b, in b); ExpectNotAliased(in a, in b); } [TestCompiler(4, 13.53f)] public static void CheckNoAliasRefsAcrossTypes([NoAlias] ref int a, ref float b) { ExpectNotAliased(in a, in b); } [TestCompiler(typeof(Union.Provider))] public static void CheckNoAliasRefsInUnion(ref Union u) { ExpectAliased(in u.a, in u.b); ExpectAliased(in u.a, in u.c); ExpectNotAliased(in u.b, in u.c); } [TestCompiler(typeof(ContainerOfManyNoAliasFields.Provider))] public static unsafe void CheckNoAliasOfSubStructs(ref ContainerOfManyNoAliasFields s) { // Since ptr1 and ptr2 have [NoAlias], they do not alias within the same struct instance. ExpectNotAliased(s.s0.ptr1, s.s0.ptr2); ExpectNotAliased(s.s1.ptr1, s.s1.ptr2); ExpectNotAliased(s.s2.ptr1, s.s2.ptr2); ExpectNotAliased(s.s3.ptr1, s.s3.ptr2); // Across s0 and s1 their pointers can alias each other though. ExpectAliased(s.s0.ptr1, s.s1.ptr1); ExpectAliased(s.s0.ptr1, s.s1.ptr2); ExpectAliased(s.s0.ptr2, s.s1.ptr1); ExpectAliased(s.s0.ptr2, s.s1.ptr2); // Also s2 can alias with s0 and s1 (because they do not have [NoAlias]). ExpectAliased(s.s2.ptr1, s.s0.ptr1); ExpectAliased(s.s2.ptr1, s.s0.ptr2); ExpectAliased(s.s2.ptr2, s.s1.ptr1); ExpectAliased(s.s2.ptr2, s.s1.ptr2); // Also s3 can alias with s0 and s1 (because they do not have [NoAlias]). ExpectAliased(s.s3.ptr1, s.s0.ptr1); ExpectAliased(s.s3.ptr1, s.s0.ptr2); ExpectAliased(s.s3.ptr2, s.s1.ptr1); ExpectAliased(s.s3.ptr2, s.s1.ptr2); // But s2 and s3 cannot alias each other (they both have [NoAlias]). ExpectNotAliased(s.s2.ptr1, s.s3.ptr1); ExpectNotAliased(s.s2.ptr1, s.s3.ptr2); ExpectNotAliased(s.s2.ptr2, s.s3.ptr1); ExpectNotAliased(s.s2.ptr2, s.s3.ptr2); } [TestCompiler(typeof(ContainerOfManyNoAliasFields.Provider))] public static unsafe void CheckNoAliasFieldCompareWithParentStruct(ref ContainerOfManyNoAliasFields s) { s.s0.Compare(ref s); s.s1.Compare(ref s); s.s2.Compare(ref s); s.s3.Compare(ref s); } [TestCompiler(typeof(LinkedList.Provider))] public static unsafe void CheckStructPointerOfSameTypeInStruct(ref LinkedList l) { ExpectAliased(in l, l.next); } [TestCompiler(typeof(NoAliasWithContentsStruct.Provider))] public static unsafe void CheckStructWithNoAlias(ref NoAliasWithContentsStruct s) { // Since NoAliasWithContentsStruct has [NoAlias] on the struct definition, it cannot alias with any pointers within the struct. ExpectNotAliased(in s, s.ptr0); ExpectNotAliased(in s, s.ptr1); } [TestCompiler(typeof(DoesAliasWithSubStructPointersStruct.Provider))] public static unsafe void CheckStructWithNoAliasAndSubStructs(ref DoesAliasWithSubStructPointersStruct s) { // Since DoesAliasWithSubStructPointersStruct has [NoAlias] on the struct definition, it cannot alias with any pointers within the struct. ExpectNotAliased(in s, s.s); ExpectNotAliased(in s, s.ptr); // s.s is a [NoAlias] struct, so it shouldn't alias with pointers within it. ExpectNotAliased(s.s, s.s->ptr0); ExpectNotAliased(s.s, s.s->ptr1); // But we don't know whether s.s and s.ptr alias. ExpectAliased(s.s, s.ptr); // And we cannot assume that s does not alias with the sub-pointers of s.s. ExpectAliased(in s, s.s->ptr0); ExpectAliased(in s, s.s->ptr1); } private unsafe struct AliasingWithSelf { public AliasingWithSelf* ptr; [MethodImpl(MethodImplOptions.NoInlining)] public void CheckAlias() { ExpectAliased(in this, ptr); } } [TestCompiler] public static unsafe void CheckAliasingWithSelf() { var s = new AliasingWithSelf { ptr = null }; s.ptr = (AliasingWithSelf*) &s; s.CheckAlias(); } private unsafe struct AliasingWithHiddenSelf { public void* ptr; [MethodImpl(MethodImplOptions.NoInlining)] public void CheckAlias() { ExpectAliased(in this, ptr); } } [TestCompiler] public static unsafe void CheckAliasingWithHiddenSelf() { var s = new AliasingWithHiddenSelf { ptr = null }; s.ptr = &s; s.CheckAlias(); } [MethodImpl(MethodImplOptions.NoInlining)] [return: NoAlias] private static unsafe int* NoAliasReturn(int size) { return (int*)UnsafeUtility.Malloc(size, 16, Allocator.Temp); } [TestCompiler(typeof(NoAliasField.Provider))] public static unsafe void CheckNoAliasReturn(ref NoAliasField s) { int* ptr1 = NoAliasReturn(40); int* ptr2 = NoAliasReturn(4); int* ptr3 = ptr2 + 4; byte* ptr4 = (byte*)ptr3 + 1; // Obviously it still aliases with itself even it we bitcast. ExpectAliased((char*)ptr1 + 4, ptr1 + 1); // We know that both allocations can't point to the same memory as // they are derived from Malloc!). ExpectNotAliased(ptr1, ptr2); // Since ptr3 derives from ptr2 it cannot alias with ptr1. ExpectNotAliased(ptr3, ptr1); // And the derefenced memory locations at ptr3 and ptr2 cannot alias // since ptr3 does not overlap the allocation in ptr2. ExpectNotAliased(in *ptr3, in *ptr2); // The pointers pt4 and ptr3 have overlapping ranges so they do alias. ExpectAliased(in *ptr4, in *ptr3); // The pointers cannot alias with anything else too! ExpectNotAliased(ptr1, in s); ExpectNotAliased(ptr1, s.ptr1); ExpectNotAliased(ptr1, s.ptr2); ExpectNotAliased(ptr2, in s); ExpectNotAliased(ptr2, s.ptr1); ExpectNotAliased(ptr2, s.ptr2); ExpectNotAliased(ptr3, in s); ExpectNotAliased(ptr3, s.ptr1); ExpectNotAliased(ptr3, s.ptr2); ExpectNotAliased(ptr4, in s); ExpectNotAliased(ptr4, s.ptr1); ExpectNotAliased(ptr4, s.ptr2); UnsafeUtility.Free(ptr1, Allocator.Temp); UnsafeUtility.Free(ptr2, Allocator.Temp); } [TestCompiler] public static unsafe void CheckMallocIsNoAlias() { int* ptr1 = (int*)UnsafeUtility.Malloc(sizeof(int) * 4, 16, Allocator.Temp); int* ptr2 = (int*)UnsafeUtility.Malloc(sizeof(int), 16, Allocator.Temp); ExpectNotAliased(ptr1, ptr2); UnsafeUtility.Free(ptr1, Allocator.Temp); UnsafeUtility.Free(ptr2, Allocator.Temp); } [MethodImpl(MethodImplOptions.NoInlining)] [return: NoAlias] private static unsafe int* BumpAlloc(int* alloca) { int location = alloca[0]++; return alloca + location; } [TestCompiler] public static unsafe void CheckBumpAllocIsNoAlias() { int* alloca = stackalloc int[128]; // Store our size at the start of the alloca. alloca[0] = 1; int* ptr1 = BumpAlloc(alloca); int* ptr2 = BumpAlloc(alloca); // Our bump allocator will never return the same address twice. ExpectNotAliased(ptr1, ptr2); } [TestCompiler(42, 13, 56)] public static unsafe void CheckInRefOut(in int a, ref int b, out int c) { c = 42; // They obviously alias with themselves. ExpectAliased(in a, in a); ExpectAliased(in b, in b); ExpectAliased(in c, in c); // And alias with each other too. ExpectAliased(in a, in b); ExpectAliased(in a, in c); ExpectAliased(in b, in c); } [TestCompiler(42, 13)] public static unsafe void CheckOutOut(out int a, out int b) { a = 56; b = -4; ExpectAliased(in a, in b); } private struct SomeData { public int A; } [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe void OutOfBoundsGEPNoAlias(SomeData* someData) { ExpectNotAliased(in someData[0], in someData[1]); } [TestCompiler] public static unsafe void CheckOutOfBoundsGEPNoAlias() { var someData = stackalloc SomeData[2]; someData[0].A = 42; someData[1].A = 13; ExpectNotAliased(in someData[0], in someData[1]); OutOfBoundsGEPNoAlias(someData); } } }