From dd003ff59a3c4cacef603e3a312ba8e8cef7eaab Mon Sep 17 00:00:00 2001 From: Dylan Perks Date: Mon, 23 Sep 2019 17:47:22 +0100 Subject: [PATCH] Span Parameters (#91) * Implement Span parameter marshalling * Implement Span parameter test * Quality of life improvements * Fixed a bug where output IL wasn't correct * Update .travis.yml * Update .travis.yml * var and stuff * xmldoc * Update SpanMarshallingWrapper.cs * ANNOTATE * Update SpanMarshallingWrapper.cs * Update SpanMarshallingWrapper.cs * Update SpanMarshallingWrapper.cs * SEMICOLONS DAGNAMIT * Update SpanMarshallingWrapper.cs * Update AdvancedDLSupport.csproj * Create a new extension method called IsBlittable * Fix errors * Fix more errors * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml * Update .travis.yml --- .travis.yml | 2 +- .../Data/Interfaces/ISpanMarshallingTests.cs | 4 +- .../Tests/Integration/SpanMarshallingTests.cs | 13 + .../c/src/SpanMarshallingTests.c | 37 +- AdvancedDLSupport/AdvancedDLSupport.csproj | 2 +- .../Extensions/TypeExtensions.cs | 26 ++ .../Wrappers/SpanMarshallingWrapper.cs | 349 +++++++++++------- 7 files changed, 289 insertions(+), 144 deletions(-) diff --git a/.travis.yml b/.travis.yml index bd39a938..2206faf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ addons: packages: - gcc-multilib -mono: latest +mono: 6.0.0 dotnet: 2.2.402 before_install: diff --git a/AdvancedDLSupport.Tests/Data/Interfaces/ISpanMarshallingTests.cs b/AdvancedDLSupport.Tests/Data/Interfaces/ISpanMarshallingTests.cs index 04ab854b..c753e70d 100644 --- a/AdvancedDLSupport.Tests/Data/Interfaces/ISpanMarshallingTests.cs +++ b/AdvancedDLSupport.Tests/Data/Interfaces/ISpanMarshallingTests.cs @@ -18,8 +18,6 @@ // using System; -using System.Runtime.InteropServices; -using AdvancedDLSupport.Tests.TestBases; #pragma warning disable SA1600, CS1591 @@ -29,5 +27,7 @@ public interface ISpanMarshallingTests { [return: NativeCollectionLength(10)] Span GetInt32ArrayZeroToNine(); + + void WriteToInt32Array(Span span, int arrLen); } } diff --git a/AdvancedDLSupport.Tests/Tests/Integration/SpanMarshallingTests.cs b/AdvancedDLSupport.Tests/Tests/Integration/SpanMarshallingTests.cs index c2cef6fd..4ab21ec5 100644 --- a/AdvancedDLSupport.Tests/Tests/Integration/SpanMarshallingTests.cs +++ b/AdvancedDLSupport.Tests/Tests/Integration/SpanMarshallingTests.cs @@ -51,6 +51,19 @@ public void CanMarshalCollectionWithConstLength() } } + [Fact] + public void CanMarshalSpanAsPointer() + { + Span span = stackalloc int[10]; + + Library.WriteToInt32Array(span, 10); + + for (var i = 0; i < 10; i++) + { + Assert.True(span[i] == i); + } + } + [Fact] public void ThrowsWhenSpanTypeIsReferenceType() { diff --git a/AdvancedDLSupport.Tests/c/src/SpanMarshallingTests.c b/AdvancedDLSupport.Tests/c/src/SpanMarshallingTests.c index 7ca5d4dd..b96049a1 100644 --- a/AdvancedDLSupport.Tests/c/src/SpanMarshallingTests.c +++ b/AdvancedDLSupport.Tests/c/src/SpanMarshallingTests.c @@ -1,12 +1,39 @@ #include "comp.h" #include "TestStruct.h" +int32_t globalArray[10]; +int isInitialized = 0; + +void InitGlobals() +{ + if (isInitialized == 0) + { + for (int i = 0; i < 10; ++i) + { + globalArray[i] = i; + } + + isInitialized = 1; + } +} + +//Rewriten so there will not be a memory leak __declspec(dllexport) int32_t* GetInt32ArrayZeroToNine() { - int32_t* arr = malloc(sizeof(int32_t) * 10); + InitGlobals(); + + return globalArray; +} + +__declspec(dllexport) void WriteToInt32Array(int32_t* arr, int arrLen) +{ + InitGlobals(); + + int len = arrLen < 10 ? arrLen : 10; - for (int i = 0; i < 10; i++) - arr[i] = i; + for (int i = 0; i < len; ++i) + { + arr[i] = globalArray[i]; + } +} - return arr; -} \ No newline at end of file diff --git a/AdvancedDLSupport/AdvancedDLSupport.csproj b/AdvancedDLSupport/AdvancedDLSupport.csproj index f84fbb19..649e6a7b 100644 --- a/AdvancedDLSupport/AdvancedDLSupport.csproj +++ b/AdvancedDLSupport/AdvancedDLSupport.csproj @@ -4,7 +4,7 @@ $(DefineConstants);JETBRAINS_ANNOTATIONS true true - 7.1 + 7.3 Debug;Release AnyCPU;x86;x64 true diff --git a/AdvancedDLSupport/Extensions/TypeExtensions.cs b/AdvancedDLSupport/Extensions/TypeExtensions.cs index 1ddd1ecc..9e83fb6a 100644 --- a/AdvancedDLSupport/Extensions/TypeExtensions.cs +++ b/AdvancedDLSupport/Extensions/TypeExtensions.cs @@ -31,6 +31,32 @@ namespace AdvancedDLSupport.Extensions /// internal static class TypeExtensions { + /// + /// A class used for testing whether a generic argument is blittable/unmanaged. + /// + private class UnmanagedTest + where T : unmanaged + { + } + + /// + /// Determines whether the given type is blittable. + /// + /// The type to check. + /// True if the type is blittable. + public static bool IsUnmanaged([NotNull] this Type type) + { + try + { + typeof(UnmanagedTest<>).MakeGenericType(type); + return true; + } + catch + { + return false; + } + } + /// /// Determines whether the given type is a delegate type. /// diff --git a/AdvancedDLSupport/ImplementationGenerators/Wrappers/SpanMarshallingWrapper.cs b/AdvancedDLSupport/ImplementationGenerators/Wrappers/SpanMarshallingWrapper.cs index fdfd506b..c27d6890 100644 --- a/AdvancedDLSupport/ImplementationGenerators/Wrappers/SpanMarshallingWrapper.cs +++ b/AdvancedDLSupport/ImplementationGenerators/Wrappers/SpanMarshallingWrapper.cs @@ -1,135 +1,214 @@ -// -// SpanMarshallingWrapper.cs -// -// Copyright (c) 2018 Firwood Software -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this program. If not, see . -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using AdvancedDLSupport.Extensions; -using AdvancedDLSupport.Pipeline; -using AdvancedDLSupport.Reflection; -using JetBrains.Annotations; - -namespace AdvancedDLSupport.ImplementationGenerators -{ - /// - /// Generates wrapper instructions for returning from unmanaged code - /// through a pointer and provided length. - /// - internal class SpanMarshallingWrapper : CallWrapperBase - { - /// - /// Initializes a new instance of the class. - /// - /// The module where the implementation should be generated. - /// The type in which the implementation should be generated. - /// The IL generator for the target type's constructor. - /// The configuration object to use. - public SpanMarshallingWrapper - ( - [NotNull] ModuleBuilder targetModule, - [NotNull] TypeBuilder targetType, - [NotNull] ILGenerator targetTypeConstructorIL, - ImplementationOptions options - ) - : base - ( - targetModule, - targetType, - targetTypeConstructorIL, - options - ) - { - } - - /// - public override IntrospectiveMethodInfo GeneratePassthroughDefinition(PipelineWorkUnit workUnit) - { - if (!workUnit.Definition.ReturnType.GenericTypeArguments[0].IsValueType) - { - // Span is used because unbound generics are not allowed inside a nameof, and it still results as just 'Span' - throw new NotSupportedException($"Method is not a {nameof(ValueType)} and cannot be marshaled as a {nameof(Span)}. Marshalling {nameof(Span)}" + - $"requires the marshaled type T in {nameof(Span)} to be a {nameof(ValueType)}"); - } - - var definition = workUnit.Definition; - - Type newReturnType = definition.ReturnType.GenericTypeArguments[0].MakePointerType(); - - /* TODO? Add marshaling for Span<> params */ - - Type[] parametersTypes = definition.ParameterTypes.ToArray(); - - MethodBuilder passthroughMethod = TargetType.DefineMethod - ( - $"{workUnit.GetUniqueBaseMemberName()}_wrapped", - MethodAttributes.Private | MethodAttributes.Virtual | MethodAttributes.HideBySig, - CallingConventions.Standard, - newReturnType, - parametersTypes - ); - - passthroughMethod.ApplyCustomAttributesFrom(definition, newReturnType, parametersTypes); - - return new IntrospectiveMethodInfo - ( - passthroughMethod, - newReturnType, - parametersTypes, - definition.MetadataType, - definition - ); - } - - /// - public override void EmitEpilogue(ILGenerator il, PipelineWorkUnit workUnit) - { - ConstructorInfo ctor = workUnit.Definition.ReturnType.GetConstructor(new[] { typeof(void*), typeof(int) }); - - il.Emit(OpCodes.Ldc_I4, GetNativeCollectionLengthMetadata(workUnit.Definition).Length); - il.Emit(OpCodes.Newobj, ctor); - } - - private NativeCollectionLengthAttribute GetNativeCollectionLengthMetadata(IntrospectiveMethodInfo info) - { - IReadOnlyList attributes = info.ReturnParameterCustomAttributes; - - foreach (CustomAttributeData customAttributeData in attributes) - { - if (customAttributeData.AttributeType == typeof(NativeCollectionLengthAttribute)) - { - return customAttributeData.ToInstance(); - } - } - - throw new InvalidOperationException($"Method return type does not have required {nameof(NativeCollectionLengthAttribute)}"); - } - - /// - public override GeneratorComplexity Complexity => GeneratorComplexity.TransformsParameters | GeneratorComplexity.MemberDependent; - - /// - public override bool IsApplicable(IntrospectiveMethodInfo member) - { - return member.ReturnType.IsGenericType // prevents exception on the line below - && member.ReturnType.GetGenericTypeDefinition() == typeof(Span<>); - } - } -} +// +// SpanMarshallingWrapper.cs +// +// Copyright (c) 2018 Firwood Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using AdvancedDLSupport.Extensions; +using AdvancedDLSupport.Pipeline; +using AdvancedDLSupport.Reflection; +using JetBrains.Annotations; +using StrictEmit; + +namespace AdvancedDLSupport.ImplementationGenerators +{ + /// + /// Generates wrapper instructions for returning from unmanaged code + /// through a pointer and provided length. + /// + internal class SpanMarshallingWrapper : CallWrapperBase + { + /// + /// Initializes a new instance of the class. + /// + /// The module where the implementation should be generated. + /// The type in which the implementation should be generated. + /// The IL generator for the target type's constructor. + /// The configuration object to use. + public SpanMarshallingWrapper + ( + [NotNull] ModuleBuilder targetModule, + [NotNull] TypeBuilder targetType, + [NotNull] ILGenerator targetTypeConstructorIL, + ImplementationOptions options + ) + : base + ( + targetModule, + targetType, + targetTypeConstructorIL, + options + ) + { + } + + /// + public override IntrospectiveMethodInfo GeneratePassthroughDefinition(PipelineWorkUnit workUnit) + { + var returnType = workUnit.Definition.ReturnType; + Type newReturnType; + var definition = workUnit.Definition; + + if (IsSpanType(returnType)) + { + var genericType = returnType.GenericTypeArguments[0]; + + if (!genericType.IsUnmanaged()) + { + throw new NotSupportedException($"Method return type must be blittable."); + } + + newReturnType = genericType.MakePointerType(); + } + else + { + newReturnType = returnType; + } + + var parametersTypes = definition.ParameterTypes.ToArray(); + + for (int i = 0; i < parametersTypes.Length; ++i) + { + var paramType = parametersTypes[i]; + if (IsSpanType(paramType)) + { + var genericParam = paramType.GenericTypeArguments[0]; + + if (genericParam.IsGenericType) + { + throw new NotSupportedException("Generic type found as Span generic argument"); + } + + if (!genericParam.IsUnmanaged()) + { + throw new NotSupportedException("Reference or value type containing references found in Span or ReadOnlySpan generic parameter."); + } + + parametersTypes[i] = genericParam.MakePointerType(); // genercParam.MakePointerType(); + } + } + + MethodBuilder passthroughMethod = TargetType.DefineMethod + ( + $"{workUnit.GetUniqueBaseMemberName()}_wrapped", + MethodAttributes.Private | MethodAttributes.Virtual | MethodAttributes.HideBySig, + CallingConventions.Standard, + newReturnType, + parametersTypes + ); + + passthroughMethod.ApplyCustomAttributesFrom(definition, newReturnType, parametersTypes); + + return new IntrospectiveMethodInfo + ( + passthroughMethod, + newReturnType, + parametersTypes, + definition.MetadataType, + definition + ); + } + + /// + public override void EmitPrologue(ILGenerator il, PipelineWorkUnit workUnit) + { + var definition = workUnit.Definition; + + var parameterTypes = definition.ParameterTypes; + + il.EmitLoadArgument(0); + + for (short i = 1; i <= parameterTypes.Count; ++i) + { + var paramType = parameterTypes[i - 1]; + + if (IsSpanType(paramType)) + { + var pinnedLocal = il.DeclareLocal(paramType.GenericTypeArguments[0].MakeByRefType(), true); + + var getPinnableReferenceMethod = paramType.GetMethod(nameof(Span.GetPinnableReference), BindingFlags.Public | BindingFlags.Instance); + + il.EmitLoadArgumentAddress(i); + il.EmitCallDirect(getPinnableReferenceMethod); + il.EmitDuplicate(); + il.EmitSetLocalVariable(pinnedLocal); + il.EmitConvertToNativeInt(); + } + else + { + il.EmitLoadArgument(i); + } + } + } + + /// + public override void EmitEpilogue(ILGenerator il, PipelineWorkUnit workUnit) + { + var returnType = workUnit.Definition.ReturnType; + + if (IsSpanType(returnType)) + { + il.EmitConstantInt(GetNativeCollectionLengthMetadata(workUnit.Definition).Length); + il.EmitNewObject(returnType.GetConstructor(new[] { typeof(void*), typeof(int) })); + } + } + + private NativeCollectionLengthAttribute GetNativeCollectionLengthMetadata(IntrospectiveMethodInfo info) + { + IReadOnlyList attributes = info.ReturnParameterCustomAttributes; + + foreach (CustomAttributeData customAttributeData in attributes) + { + if (customAttributeData.AttributeType == typeof(NativeCollectionLengthAttribute)) + { + return customAttributeData.ToInstance(); + } + } + + throw new InvalidOperationException($"Method return type does not have required {nameof(NativeCollectionLengthAttribute)}"); + } + + /// + public override GeneratorComplexity Complexity => GeneratorComplexity.TransformsParameters | GeneratorComplexity.MemberDependent; + + /// + public override bool IsApplicable(IntrospectiveMethodInfo member) + { + return IsSpanType(member.ReturnType) || member.ParameterTypes.Any(IsSpanType); + } + + /// + /// Determines whether the provided is a generic span. + /// + /// The type to check. + private static bool IsSpanType([NotNull] Type type) + { + if (type.IsGenericType) + { + var generic = type.GetGenericTypeDefinition(); + return generic == typeof(Span<>) || generic == typeof(ReadOnlySpan<>); + } + + return false; + } + } +}