File: Marshalling\StatelessMarshallingStrategy.cs
Web Access
Project: Microsoft.Interop.SourceGeneration.csproj (Microsoft.Interop.SourceGeneration)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Interop
{
    /// <summary>
    /// Stateless marshalling support for a type that has a custom unmanaged type.
    /// </summary>
    internal sealed class StatelessValueMarshalling(TypePositionInfo info, StubCodeContext codeContext, string marshallerType, ManagedTypeInfo unmanagedType, MarshallerShape shape) : ICustomTypeMarshallingStrategy
    {
        public ManagedTypeInfo NativeType => unmanagedType;
        public bool UsesNativeIdentifier => true;
        public TypePositionInfo TypeInfo => info;
        public StubCodeContext CodeContext => codeContext;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.GuaranteedUnmarshal))
            {
                (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
                writer.WriteLine($"{managedIdentifier} = {marshallerType}.{ShapeMemberNames.Value.Stateless.ConvertToManagedFinally}({nativeIdentifier});");
            }
        }

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (!shape.HasFlag(MarshallerShape.ToUnmanaged) && !shape.HasFlag(MarshallerShape.CallerAllocatedBuffer))
            {
                return;
            }

            (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
            string convertToUnmanaged = $"{marshallerType}.{ShapeMemberNames.Value.Stateless.ConvertToUnmanaged}({managedIdentifier})";
            if (unmanagedType == SpecialTypeInfo.Void)
            {
                // Exception marshallers can marshal to void without producing a native value.
                writer.WriteLine($"{convertToUnmanaged};");
                return;
            }

            // Some exception marshallers return nint even when the signature's native type is a pointer.
            string cast = unmanagedType is PointerTypeInfo ? $"({unmanagedType.FullTypeName})" : string.Empty;
            writer.WriteLine($"{nativeIdentifier} = {cast}{convertToUnmanaged};");
        }

        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.ToManaged))
            {
                (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
                writer.WriteLine($"{managedIdentifier} = {marshallerType}.{ShapeMemberNames.Value.Stateless.ConvertToManaged}({nativeIdentifier});");
            }
        }

        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }
    }

    /// <summary>
    /// Marshaller that enables support for caller-allocated stack buffers.
    /// </summary>
    internal sealed class StatelessCallerAllocatedBufferMarshalling(ICustomTypeMarshallingStrategy innerMarshaller, string marshallerType, string bufferElementType, bool isLinearCollectionMarshalling) : ICustomTypeMarshallingStrategy
    {
        public ManagedTypeInfo NativeType => innerMarshaller.NativeType;
        public TypePositionInfo TypeInfo => innerMarshaller.TypeInfo;
        public StubCodeContext CodeContext => innerMarshaller.CodeContext;
        public bool UsesNativeIdentifier => innerMarshaller.UsesNativeIdentifier;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateCleanupCallerAllocatedResourcesStatements(writer, context);
        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateCleanupCalleeAllocatedResourcesStatements(writer, context);
        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateGuaranteedUnmarshalStatements(writer, context);

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (!MarshallerHelpers.CanUseCallerAllocatedBuffer(TypeInfo, CodeContext))
            {
                innerMarshaller.GenerateMarshalStatements(writer, context);
                return;
            }

            string bufferIdentifier = context.GetAdditionalIdentifier(TypeInfo, "buffer");
            writer.WriteLine($"{TypeNames.System_Span}<{bufferElementType}> {bufferIdentifier} = stackalloc {bufferElementType}[{marshallerType}.{ShapeMemberNames.BufferSize}];");

            (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(TypeInfo);
            if (isLinearCollectionMarshalling)
            {
                string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                writer.WriteLine($"{nativeIdentifier} = {marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.AllocateContainerForUnmanagedElements}({managedIdentifier}, {bufferIdentifier}, out {numElementsIdentifier});");
                innerMarshaller.GenerateMarshalStatements(writer, context);
            }
            else
            {
                writer.WriteLine($"{nativeIdentifier} = {marshallerType}.{ShapeMemberNames.Value.Stateless.ConvertToUnmanaged}({managedIdentifier}, {bufferIdentifier});");
            }
        }

        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinnedMarshalStatements(writer, context);
        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinStatements(writer, context);
        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateSetupStatements(writer, context);
        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalCaptureStatements(writer, context);
        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalStatements(writer, context);
        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateNotifyForSuccessfulInvokeStatements(writer, context);
    }

    internal sealed class StatelessFreeMarshalling(ICustomTypeMarshallingStrategy innerMarshaller, string marshallerType) : ICustomTypeMarshallingStrategy
    {
        public bool UsesNativeIdentifier => innerMarshaller.UsesNativeIdentifier;
        public TypePositionInfo TypeInfo => innerMarshaller.TypeInfo;
        public StubCodeContext CodeContext => innerMarshaller.CodeContext;
        public ManagedTypeInfo NativeType => innerMarshaller.NativeType;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is not StubIdentifierContext.Stage.CleanupCallerAllocated)
            {
                return;
            }

            innerMarshaller.GenerateCleanupCallerAllocatedResourcesStatements(writer, context);
            writer.WriteLine($"{marshallerType}.{ShapeMemberNames.Free}({context.GetIdentifiers(TypeInfo).native});");
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is not StubIdentifierContext.Stage.CleanupCalleeAllocated)
            {
                return;
            }

            innerMarshaller.GenerateCleanupCalleeAllocatedResourcesStatements(writer, context);
            writer.WriteLine($"{marshallerType}.{ShapeMemberNames.Free}({context.GetIdentifiers(TypeInfo).native});");
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateGuaranteedUnmarshalStatements(writer, context);
        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateMarshalStatements(writer, context);
        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateNotifyForSuccessfulInvokeStatements(writer, context);
        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinnedMarshalStatements(writer, context);
        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GeneratePinStatements(writer, context);
        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateSetupStatements(writer, context);
        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalCaptureStatements(writer, context);
        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => innerMarshaller.GenerateUnmarshalStatements(writer, context);
    }

    /// <summary>
    /// Allocates storage through a stateless linear collection marshaller.
    /// </summary>
    internal sealed class StatelessLinearCollectionSpaceAllocator(TypePositionInfo info, StubCodeContext codeContext, string marshallerType, ManagedTypeInfo unmanagedType, MarshallerShape shape, CountInfo countInfo, bool countInfoRequiresCast) : ICustomTypeMarshallingStrategy
    {
        public ManagedTypeInfo NativeType => unmanagedType;
        public TypePositionInfo TypeInfo => info;
        public StubCodeContext CodeContext => codeContext;
        public bool UsesNativeIdentifier => true;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is StubIdentifierContext.Stage.CleanupCallerAllocated
                && MarshallerHelpers.GetMarshalDirection(TypeInfo, CodeContext) != MarshalDirection.ManagedToUnmanaged)
            {
                string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, countInfoRequiresCast, CodeContext, context)};");
            }
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is StubIdentifierContext.Stage.CleanupCalleeAllocated
                && MarshallerHelpers.GetMarshalDirection(TypeInfo, CodeContext) != MarshalDirection.ManagedToUnmanaged)
            {
                string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, countInfoRequiresCast, CodeContext, context)};");
            }
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (!shape.HasFlag(MarshallerShape.GuaranteedUnmarshal))
            {
                return;
            }

            (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(TypeInfo);
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
            writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, countInfoRequiresCast, CodeContext, context)};");
            writer.WriteLine($"{managedIdentifier} = {marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.AllocateContainerForManagedElementsFinally}({nativeIdentifier}, {numElementsIdentifier});");
        }

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (shape.HasFlag(MarshallerShape.ToUnmanaged)
                && !(shape.HasFlag(MarshallerShape.CallerAllocatedBuffer)
                    && MarshallerHelpers.CanUseCallerAllocatedBuffer(TypeInfo, CodeContext)))
            {
                (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(TypeInfo);
                string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                writer.WriteLine($"{nativeIdentifier} = {marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.AllocateContainerForUnmanagedElements}({managedIdentifier}, out {numElementsIdentifier});");
            }
        }

        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
            writer.WriteLine($"int {numElementsIdentifier};");
            writer.WriteLine(MarshallerHelpers.SkipInitOrDefaultInit(
                new TypePositionInfo(SpecialTypeInfo.Int32, NoMarshallingInfo.Instance)
                {
                    InstanceIdentifier = numElementsIdentifier
                }, context));
        }

        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out))
            {
                return;
            }
            if (!shape.HasFlag(MarshallerShape.ToManaged))
            {
                return;
            }

            (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(TypeInfo);
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
            writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, countInfoRequiresCast, CodeContext, context)};");
            writer.WriteLine($"{managedIdentifier} = {marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.AllocateContainerForManagedElements}({nativeIdentifier}, {numElementsIdentifier});");
        }
    }

    internal sealed class StatelessLinearCollectionSource(TypePositionInfo info, StubCodeContext codeContext, string marshallerType) : IElementsMarshallingCollectionSource
    {
        public TypePositionInfo TypeInfo => info;
        public StubCodeContext CodeContext => codeContext;

        public string GetUnmanagedValuesDestination(StubIdentifierContext context)
        {
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context);
            string nativeIdentifier = context.GetIdentifiers(info).native;
            return $"{marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.GetUnmanagedValuesDestination}({nativeIdentifier}, {numElementsIdentifier})";
        }

        public string GetManagedValuesSource(StubIdentifierContext context)
        {
            string managedIdentifier = context.GetIdentifiers(info).managed;
            return $"{marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.GetManagedValuesSource}({managedIdentifier})";
        }

        public string GetUnmanagedValuesSource(StubIdentifierContext context)
        {
            string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(info, context);
            string nativeIdentifier = context.GetIdentifiers(info).native;
            return $"{marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.GetUnmanagedValuesSource}({nativeIdentifier}, {numElementsIdentifier})";
        }

        public string GetManagedValuesDestination(StubIdentifierContext context)
        {
            string managedIdentifier = context.GetIdentifiers(info).managed;
            return $"{marshallerType}.{ShapeMemberNames.LinearCollection.Stateless.GetManagedValuesDestination}({managedIdentifier})";
        }
    }

    /// <summary>
    /// Marshals collection elements using storage supplied by a stateless collection marshaller.
    /// </summary>
    internal sealed class StatelessLinearCollectionMarshalling(
        ICustomTypeMarshallingStrategy spaceMarshallingStrategy,
        ElementsMarshalling elementsMarshalling,
        ManagedTypeInfo unmanagedType,
        MarshallerShape shape,
        CountInfo countInfo,
        bool castCountInfo,
        bool cleanupElementsAndSpace) : ICustomTypeMarshallingStrategy
    {
        public bool UsesNativeIdentifier => true;
        public TypePositionInfo TypeInfo => spaceMarshallingStrategy.TypeInfo;
        public StubCodeContext CodeContext => spaceMarshallingStrategy.CodeContext;
        public ManagedTypeInfo NativeType => unmanagedType;

        public void GenerateCleanupCallerAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (!cleanupElementsAndSpace)
            {
                return;
            }

            var cleanupWriter = new IndentedTextWriter();
            elementsMarshalling.GenerateElementCleanupStatement(cleanupWriter, context);
            if (cleanupWriter.Length != 0)
            {
                if (!CodeContext.AdditionalTemporaryStateLivesAcrossStages)
                {
                    string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                    if (countInfo is NoCountInfo && MarshallerHelpers.GetMarshalDirection(TypeInfo, CodeContext) == MarshalDirection.ManagedToUnmanaged)
                    {
                        // The count is unavailable in this nested cleanup case.
                        // See https://github.com/dotnet/runtime/issues/93423.
                        writer.WriteLine($"{numElementsIdentifier} = 0;");
                    }
                    else
                    {
                        writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, castCountInfo, CodeContext, context)};");
                    }
                }
                writer.Write(cleanupWriter.ToString());
            }

            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is StubIdentifierContext.Stage.CleanupCallerAllocated)
            {
                spaceMarshallingStrategy.GenerateCleanupCallerAllocatedResourcesStatements(writer, context);
            }
        }

        public void GenerateCleanupCalleeAllocatedResourcesStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (!cleanupElementsAndSpace)
            {
                return;
            }

            var cleanupWriter = new IndentedTextWriter();
            elementsMarshalling.GenerateElementCleanupStatement(cleanupWriter, context);
            if (cleanupWriter.Length != 0)
            {
                if (!CodeContext.AdditionalTemporaryStateLivesAcrossStages)
                {
                    string numElementsIdentifier = MarshallerHelpers.GetNumElementsIdentifier(TypeInfo, context);
                    writer.WriteLine($"{numElementsIdentifier} = {ElementsMarshalling.GenerateNumElementsExpression(countInfo, castCountInfo, CodeContext, context)};");
                }
                writer.Write(cleanupWriter.ToString());
            }

            if (MarshallerHelpers.GetCleanupStage(TypeInfo, CodeContext) is StubIdentifierContext.Stage.CleanupCallerAllocated)
            {
                spaceMarshallingStrategy.GenerateCleanupCalleeAllocatedResourcesStatements(writer, context);
            }
        }

        public void GenerateGuaranteedUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => spaceMarshallingStrategy.GenerateGuaranteedUnmarshalStatements(writer, context);

        public void GenerateMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind == ByValueContentsMarshalKind.Out)
            {
                elementsMarshalling.GenerateClearUnmanagedDestination(writer, context);
                return;
            }
            if (CodeContext.Direction == MarshalDirection.UnmanagedToManaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out))
            {
                elementsMarshalling.GenerateUnmanagedToManagedByValueOutMarshalStatement(writer, context);
                return;
            }

            spaceMarshallingStrategy.GenerateMarshalStatements(writer, context);
            if (shape.HasFlag(MarshallerShape.ToUnmanaged) || shape.HasFlag(MarshallerShape.CallerAllocatedBuffer))
            {
                elementsMarshalling.GenerateMarshalStatement(writer, context);
            }
        }

        public void GenerateNotifyForSuccessfulInvokeStatements(IndentedTextWriter writer, StubIdentifierContext context) => spaceMarshallingStrategy.GenerateNotifyForSuccessfulInvokeStatements(writer, context);
        public void GeneratePinnedMarshalStatements(IndentedTextWriter writer, StubIdentifierContext context) => spaceMarshallingStrategy.GeneratePinnedMarshalStatements(writer, context);
        public void GeneratePinStatements(IndentedTextWriter writer, StubIdentifierContext context) => spaceMarshallingStrategy.GeneratePinStatements(writer, context);

        public void GenerateSetupStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            spaceMarshallingStrategy.GenerateSetupStatements(writer, context);
            elementsMarshalling.GenerateSetupStatement(writer, context);
        }

        public void GenerateUnmarshalCaptureStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
        }

        public void GenerateUnmarshalStatements(IndentedTextWriter writer, StubIdentifierContext context)
        {
            if (CodeContext.Direction == MarshalDirection.ManagedToUnmanaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out))
            {
                elementsMarshalling.GenerateManagedToUnmanagedByValueOutUnmarshalStatement(writer, context);
                return;
            }
            if (CodeContext.Direction == MarshalDirection.UnmanagedToManaged && !TypeInfo.IsByRef && TypeInfo.ByValueContentsMarshalKind == ByValueContentsMarshalKind.Out)
            {
                elementsMarshalling.GenerateClearManagedValuesDestination(writer, context);
                spaceMarshallingStrategy.GenerateUnmarshalStatements(writer, context);
                return;
            }
            if (!shape.HasFlag(MarshallerShape.ToManaged))
            {
                return;
            }

            spaceMarshallingStrategy.GenerateUnmarshalStatements(writer, context);
            elementsMarshalling.GenerateUnmarshalStatement(writer, context);
        }
    }
}