Singularity/Library/PackageCache/com.unity.burst@1.8.4/Tests/Runtime/FunctionPointerTests.cs
2024-05-06 11:45:45 -07:00

237 lines
8.3 KiB
C#

using System;
using System.Runtime.InteropServices;
using AOT;
using NUnit.Framework;
using Unity.Burst;
using UnityEngine;
using UnityEngine.TestTools;
#if UNITY_2021_2_OR_NEWER
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
#if UNITY_EDITOR
using OverloadedFunctionPointers;
#endif
#endif
[TestFixture, BurstCompile]
public class FunctionPointerTests
{
[BurstCompile(CompileSynchronously = true)]
private static T StaticFunctionNoArgsGenericReturnType<T>()
{
return default;
}
private delegate int DelegateNoArgsIntReturnType();
[Test]
public void TestCompileFunctionPointerNoArgsGenericReturnType()
{
Assert.Throws<InvalidOperationException>(
() => BurstCompiler.CompileFunctionPointer<DelegateNoArgsIntReturnType>(StaticFunctionNoArgsGenericReturnType<int>),
"The method `Int32 StaticFunctionNoArgsGenericReturnType[Int32]()` must be a non-generic method");
}
[BurstCompile(CompileSynchronously = true)]
private static int StaticFunctionConcreteReturnType()
{
return default;
}
private delegate T DelegateGenericReturnType<T>();
[Test]
public void TestCompileFunctionPointerDelegateNoArgsGenericReturnType()
{
Assert.Throws<InvalidOperationException>(
() => BurstCompiler.CompileFunctionPointer<DelegateGenericReturnType<int>>(StaticFunctionConcreteReturnType),
"The delegate type `FunctionPointerTests+DelegateGenericReturnType`1[System.Int32]` must be a non-generic type");
}
private static class GenericClass<T>
{
public delegate int DelegateNoArgsIntReturnType();
}
[Test]
public void TestCompileFunctionPointerDelegateNoArgsGenericDeclaringType()
{
Assert.Throws<InvalidOperationException>(
() => BurstCompiler.CompileFunctionPointer<GenericClass<int>.DelegateNoArgsIntReturnType>(StaticFunctionConcreteReturnType),
"The delegate type `FunctionPointerTests+GenericClass`1+DelegateNoArgsIntReturnType[System.Int32]` must be a non-generic type");
}
// Doesn't work with IL2CPP yet - waiting for Unity fix to land. Once it does, remove `&& UNITY_EDITOR`
#if UNITY_2021_2_OR_NEWER && UNITY_EDITOR
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
[BurstCompile]
private static int CSharpFunctionPointerCallback(int value) => value * 2;
[BurstCompile(CompileSynchronously = true)]
public unsafe struct StructWithCSharpFunctionPointer : IJob
{
[NativeDisableUnsafePtrRestriction]
[ReadOnly]
public IntPtr Callback;
[ReadOnly]
public NativeArray<int> Input;
[WriteOnly]
public NativeArray<int> Output;
public void Execute()
{
delegate* unmanaged[Cdecl]<int, int> callback = (delegate* unmanaged[Cdecl]<int, int>)Callback;
Output[0] = callback(Input[0]);
}
}
[Test]
public unsafe void CSharpFunctionPointerInsideJobStructTest()
{
using (var input = new NativeArray<int>(new int[1] { 40 }, Allocator.Persistent))
using (var output = new NativeArray<int>(new int[1], Allocator.Persistent))
{
delegate* unmanaged[Cdecl]<int, int> callback = &CSharpFunctionPointerCallback;
var job = new StructWithCSharpFunctionPointer
{
Callback = (IntPtr)callback,
Input = input,
Output = output
};
job.Run();
Assert.AreEqual(40 * 2, output[0]);
}
}
[Test]
public unsafe void CSharpFunctionPointerInStaticMethodSignature()
{
var fp = BurstCompiler.CompileFunctionPointer<DelegateWithCSharpFunctionPointerParameter>(EntryPointWithCSharpFunctionPointerParameter);
delegate* unmanaged[Cdecl]<int, int> callback = &CSharpFunctionPointerCallback;
var result = fp.Invoke((IntPtr)callback);
Assert.AreEqual(10, result);
}
[BurstCompile(CompileSynchronously = true)]
private static unsafe int EntryPointWithCSharpFunctionPointerParameter(IntPtr callback)
{
delegate* unmanaged[Cdecl]<int, int> typedCallback = (delegate* unmanaged[Cdecl]<int, int>)callback;
return typedCallback(5);
}
private unsafe delegate int DelegateWithCSharpFunctionPointerParameter(IntPtr callback);
[Test]
public unsafe void FunctionPointerReturnedFromBurstFunction()
{
var fp = BurstCompiler.CompileFunctionPointer<DelegateWithCSharpFunctionPointerReturn>(EntryPointWithCSharpFunctionPointerReturn);
var fpInner = fp.Invoke();
delegate* unmanaged[Cdecl]<float, float, float, float, float, float, float> callback = (delegate* unmanaged[Cdecl]<float, float, float, float, float, float, float>)fpInner;
var result = callback(1, 2, 4, 8, 16, 32);
Assert.AreEqual((float)(1 + 2 + 4 + 8 + 16 + 32), result);
}
[BurstCompile(CompileSynchronously = true)]
private static unsafe IntPtr EntryPointWithCSharpFunctionPointerReturn()
{
delegate* unmanaged[Cdecl]<float, float, float, float, float, float, float> fp = &EntryPointWithCSharpFunctionPointerReturnHelper;
return (IntPtr)fp;
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
[BurstCompile(CompileSynchronously = true)]
private static unsafe float EntryPointWithCSharpFunctionPointerReturnHelper(float p1, float p2, float p3, float p4, float p5, float p6)
{
return p1 + p2 + p3 + p4 + p5 + p6;
}
[BurstCompile]
[UnmanagedCallersOnly(CallConvs = new [] {typeof(CallConvCdecl)})]
static long UnmanagedFunction(long burstCount) => 1;
[BurstCompile]
static unsafe void GetUnmanagedCallableWithReturn(out Callable fn)
{
fn = Callable.Create<long, long>(&UnmanagedFunction);
}
[Test]
[UnityPlatform(RuntimePlatform.WindowsEditor, RuntimePlatform.OSXEditor, RuntimePlatform.LinuxEditor)]
public void CallOverloadedFunctionWithFpArg()
{
GetUnmanagedCallableWithReturn(out var a);
Assert.AreEqual(3, a.Value);
}
private delegate int Doer(int x);
static int DoCompileFunctionPointerNestedStaticMethod(int x)
{
[BurstCompile]
static int DoIt(int x) => x * 2 - 1;
return BurstCompiler.CompileFunctionPointer<Doer>(DoIt).Invoke(x);
}
[Test]
public void TestCompileFunctionPointerNestedStaticMethod()
{
Assert.AreEqual(3, DoCompileFunctionPointerNestedStaticMethod(2));
}
private unsafe delegate IntPtr DelegateWithCSharpFunctionPointerReturn();
// Note that there are 6 float parameters to try to catch any issues with calling conventions.
private unsafe delegate float DelegateWithCSharpFunctionPointerReturnHelper(float p1, float p2, float p3, float p4, float p5, float p6);
#endif
[Test]
public void TestDelegateWithCustomAttributeThatIsNotUnmanagedFunctionPointerAttribute()
{
var fp = BurstCompiler.CompileFunctionPointer<TestDelegateWithCustomAttributeThatIsNotUnmanagedFunctionPointerAttributeDelegate>(TestDelegateWithCustomAttributeThatIsNotUnmanagedFunctionPointerAttributeHelper);
var result = fp.Invoke(42);
Assert.AreEqual(43, result);
}
[BurstCompile(CompileSynchronously = true)]
private static int TestDelegateWithCustomAttributeThatIsNotUnmanagedFunctionPointerAttributeHelper(int x) => x + 1;
[MyCustomAttribute("Foo")]
private delegate int TestDelegateWithCustomAttributeThatIsNotUnmanagedFunctionPointerAttributeDelegate(int x);
private sealed class MyCustomAttributeAttribute : Attribute
{
public MyCustomAttributeAttribute(string param) { }
}
}
#if UNITY_2021_2_OR_NEWER
// UnmanagedCallersOnlyAttribute is new in .NET 5.0. This attribute is required
// when you declare an unmanaged function pointer with an explicit calling convention.
// Fortunately, Roslyn lets us declare the attribute class ourselves, and it will be used.
// Users will need this same declaration in their own projects, in order to use
// C# 9.0 function pointers.
namespace System.Runtime.InteropServices
{
[AttributeUsage(System.AttributeTargets.Method, Inherited = false)]
public sealed class UnmanagedCallersOnlyAttribute : Attribute
{
public Type[] CallConvs;
}
}
#endif