No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

036-CSharpFunctionPointers.cs 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Doesn't work with IL2CPP yet - waiting for Unity fix to land.
  2. #if BURST_INTERNAL //|| UNITY_2021_2_OR_NEWER
  3. using System;
  4. using System.Runtime.CompilerServices;
  5. using System.Runtime.InteropServices;
  6. using NUnit.Framework;
  7. using Unity.Burst;
  8. using UnityBenchShared;
  9. #if BURST_INTERNAL
  10. using System.IO;
  11. using System.Reflection;
  12. using Burst.Compiler.IL.Aot;
  13. #endif
  14. namespace Burst.Compiler.IL.Tests
  15. {
  16. [RestrictPlatform("Mono on linux crashes to what appears to be a mono bug", Platform.Linux, exclude: true)]
  17. internal class TestCSharpFunctionPointers
  18. {
  19. [TestCompiler]
  20. public static unsafe int TestCSharpFunctionPointer()
  21. {
  22. delegate* unmanaged[Cdecl]<int, int> callback = &TestCSharpFunctionPointerCallback;
  23. return TestCSharpFunctionPointerHelper(callback);
  24. }
  25. private static unsafe int TestCSharpFunctionPointerHelper(delegate* unmanaged[Cdecl]<int, int> callback)
  26. {
  27. return callback(5);
  28. }
  29. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  30. private static int TestCSharpFunctionPointerCallback(int value) => value + 1;
  31. [TestCompiler]
  32. public static unsafe int TestCSharpFunctionPointerCastingParameterPtrFromVoid()
  33. {
  34. delegate* unmanaged[Cdecl]<void*, int> callback = &TestCSharpFunctionPointerCallbackVoidPtr;
  35. delegate* unmanaged[Cdecl]<int*, int> callbackCasted = callback;
  36. int i = 5;
  37. return callbackCasted(&i);
  38. }
  39. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  40. private static unsafe int TestCSharpFunctionPointerCallbackVoidPtr(void* value) => *((int*)value) + 1;
  41. [TestCompiler]
  42. public static unsafe int TestCSharpFunctionPointerCastingParameterPtrToVoid()
  43. {
  44. delegate* unmanaged[Cdecl]<int*, int> callback = &TestCSharpFunctionPointerCallbackIntPtr;
  45. delegate* unmanaged[Cdecl]<void*, int> callbackCasted = (delegate* unmanaged[Cdecl]<void*, int>)callback;
  46. int i = 5;
  47. return callbackCasted(&i);
  48. }
  49. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  50. private static unsafe int TestCSharpFunctionPointerCallbackIntPtr(int* value) => *value + 1;
  51. [TestCompiler]
  52. public static unsafe int TestCSharpFunctionPointerCastingToAndFromVoidPtr()
  53. {
  54. delegate* unmanaged[Cdecl]<int*, int> callback = &TestCSharpFunctionPointerCallbackIntPtr;
  55. void* callbackAsVoidPtr = callback;
  56. delegate* unmanaged[Cdecl]<int*, int> callbackCasted = (delegate* unmanaged[Cdecl]<int*, int>)callbackAsVoidPtr;
  57. int i = 5;
  58. return callbackCasted(&i);
  59. }
  60. public struct CSharpFunctionPointerProvider : IArgumentProvider
  61. {
  62. public unsafe object Value
  63. {
  64. get
  65. {
  66. delegate* unmanaged[Cdecl]<int, int> callback = &TestCSharpFunctionPointerCallback;
  67. return (IntPtr)callback;
  68. }
  69. }
  70. }
  71. [TestCompiler(typeof(CSharpFunctionPointerProvider))]
  72. public static unsafe int TestCSharpFunctionPointerPassedInFromOutside(IntPtr callbackAsIntPtr)
  73. {
  74. delegate* unmanaged[Cdecl]<int, int> callback = (delegate* unmanaged[Cdecl]<int, int>)callbackAsIntPtr;
  75. return TestCSharpFunctionPointerHelper(callback);
  76. }
  77. private struct TestCSharpFunctionPointerWithStructParameterStruct
  78. {
  79. public int X;
  80. }
  81. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  82. private static int TestCSharpFunctionPointerWithStructParameterCallback(TestCSharpFunctionPointerWithStructParameterStruct value) => value.X + 1;
  83. public struct CSharpFunctionPointerWithStructParameterProvider : IArgumentProvider
  84. {
  85. public unsafe object Value
  86. {
  87. get
  88. {
  89. delegate* unmanaged[Cdecl]<TestCSharpFunctionPointerWithStructParameterStruct, int> callback = &TestCSharpFunctionPointerWithStructParameterCallback;
  90. return (IntPtr)callback;
  91. }
  92. }
  93. }
  94. [TestCompiler(typeof(CSharpFunctionPointerWithStructParameterProvider))]
  95. public static unsafe int TestCSharpFunctionPointerPassedInFromOutsideWithStructParameter(IntPtr untypedFp)
  96. {
  97. return TestHashingFunctionPointerTypeHelper((delegate* unmanaged[Cdecl]<TestCSharpFunctionPointerWithStructParameterStruct, int>)untypedFp);
  98. }
  99. private static unsafe int TestHashingFunctionPointerTypeHelper(delegate* unmanaged[Cdecl]<TestCSharpFunctionPointerWithStructParameterStruct, int> fp)
  100. {
  101. return fp(new TestCSharpFunctionPointerWithStructParameterStruct { X = 42 });
  102. }
  103. [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_CalliNonCCallingConventionNotSupported)]
  104. public static unsafe int TestCSharpFunctionPointerInvalidCallingConvention()
  105. {
  106. delegate*<int, int> callback = &TestCSharpFunctionPointerInvalidCallingConventionCallback;
  107. return callback(5);
  108. }
  109. private static int TestCSharpFunctionPointerInvalidCallingConventionCallback(int value) => value + 1;
  110. [TestCompiler]
  111. public static unsafe int TestCSharpFunctionPointerMissingBurstCompileAttribute()
  112. {
  113. delegate* unmanaged[Cdecl]<int, int> callback = &TestCSharpFunctionPointerCallbackMissingBurstCompileAttribute;
  114. return callback(5);
  115. }
  116. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  117. private static int TestCSharpFunctionPointerCallbackMissingBurstCompileAttribute(int value) => value + 1;
  118. [Test]
  119. public unsafe void TestFunctionPointerReturnedFromBurstFunction()
  120. {
  121. #if BURST_INTERNAL
  122. var libraryCacheFolderName = Path.Combine(
  123. Path.GetDirectoryName(GetType().Assembly.Location),
  124. nameof(TestCSharpFunctionPointers),
  125. nameof(TestFunctionPointerReturnedFromBurstFunction));
  126. if (Directory.Exists(libraryCacheFolderName))
  127. {
  128. Directory.Delete(libraryCacheFolderName, true);
  129. }
  130. using var globalContext = new Server.GlobalContext(libraryCacheFolderName);
  131. var jitOptions = new AotCompilerOptions();
  132. using var methodCompiler = new Helpers.MethodCompiler(globalContext, jitOptions.BackendName, name => IntPtr.Zero);
  133. BurstCompiler.InternalCompiler = del =>
  134. {
  135. var getMethod = del.GetType().GetMethod("get_Method", BindingFlags.Public | BindingFlags.Instance);
  136. var methodInfo = (MethodInfo)getMethod.Invoke(del, new object[0]);
  137. var compiledResult = methodCompiler.CompileMethod(methodInfo, jitOptions);
  138. return compiledResult.FunctionPointer;
  139. };
  140. #endif
  141. var fp = BurstCompiler.CompileFunctionPointer<DelegateWithCSharpFunctionPointerReturn>(EntryPointWithCSharpFunctionPointerReturn);
  142. var fpInner = fp.Invoke();
  143. delegate* unmanaged[Cdecl]<float, float, float, float, float, float, float> callback = (delegate* unmanaged[Cdecl]<float, float, float, float, float, float, float>)fpInner;
  144. var result = callback(1, 2, 4, 8, 16, 32);
  145. Assert.AreEqual((float)(1 + 2 + 4 + 8 + 16 + 32), result);
  146. }
  147. [BurstCompile(CompileSynchronously = true)]
  148. private static unsafe IntPtr EntryPointWithCSharpFunctionPointerReturn()
  149. {
  150. delegate* unmanaged[Cdecl]<float, float, float, float, float, float, float> fp = &EntryPointWithCSharpFunctionPointerReturnHelper;
  151. return (IntPtr)fp;
  152. }
  153. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  154. private static unsafe float EntryPointWithCSharpFunctionPointerReturnHelper(float p1, float p2, float p3, float p4, float p5, float p6)
  155. {
  156. return p1 + p2 + p3 + p4 + p5 + p6;
  157. }
  158. private unsafe delegate IntPtr DelegateWithCSharpFunctionPointerReturn();
  159. // Note that there are 6 float parameters to try to catch any issues with calling conventions.
  160. private unsafe delegate float DelegateWithCSharpFunctionPointerReturnHelper(float p1, float p2, float p3, float p4, float p5, float p6);
  161. // Note that this test previously had a `out int i` parameter, but a bugfix in Roslyn
  162. // means that ref parameters in UnmanagedCallersOnly methods now result in a compilation error:
  163. // https://github.com/dotnet/roslyn/issues/57025
  164. // So we've updated this test to use a pointer.
  165. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
  166. private static unsafe void TestCSharpFunctionPointerCallbackWithOut(int* i)
  167. {
  168. TestCSharpFunctionPointerCallbackWithOut(out *i);
  169. }
  170. private static void TestCSharpFunctionPointerCallbackWithOut(out int i)
  171. {
  172. i = 42;
  173. }
  174. [TestCompiler]
  175. public static unsafe int TestCSharpFunctionPointerWithOut()
  176. {
  177. delegate* unmanaged[Cdecl]<int*, void> callback = &TestCSharpFunctionPointerCallbackWithOut;
  178. int i;
  179. callback(&i);
  180. return i;
  181. }
  182. #if BURST_TESTS_ONLY
  183. [DllImport("burst-dllimport-native")]
  184. private static extern unsafe int callFunctionPointer(delegate* unmanaged[Cdecl]<int, int> f);
  185. // Ignored on wasm since dynamic linking is not supported at present.
  186. // Override result on Mono because it throws a StackOverflowException for some reason related to the function pointer.
  187. // We should use OverrideResultOnMono, but OverrideResultOnMono still runs the managed version, which causes a crash,
  188. // so we use OverrideManagedResult.
  189. [TestCompiler(IgnoreOnPlatform = Backend.TargetPlatform.Wasm, OverrideManagedResult = 43)]
  190. public static unsafe int TestPassingFunctionPointerToNativeCode()
  191. {
  192. return callFunctionPointer(&TestCSharpFunctionPointerCallback);
  193. }
  194. #endif
  195. }
  196. }
  197. // This attribute is also included in com.unity.burst/Tests/Runtime/FunctionPointerTests.cs,
  198. // so we want to exclude it here when we're running inside Unity otherwise we'll get a
  199. // duplicate definition error.
  200. #if BURST_TESTS_ONLY && NETFRAMEWORK
  201. // UnmanagedCallersOnlyAttribute is new in .NET 5.0. This attribute is required
  202. // when you declare an unmanaged function pointer with an explicit calling convention.
  203. // Fortunately, Roslyn lets us declare the attribute class ourselves, and it will be used.
  204. // Users will need this same declaration in their own projects, in order to use
  205. // C# 9.0 function pointers.
  206. namespace System.Runtime.InteropServices
  207. {
  208. [AttributeUsage(System.AttributeTargets.Method, Inherited = false)]
  209. public sealed class UnmanagedCallersOnlyAttribute : Attribute
  210. {
  211. public Type[] CallConvs;
  212. }
  213. }
  214. #endif
  215. #endif