File: src\runtime\src\coreclr\tools\Common\TypeSystem\Interop\IL\Marshaller.Aot.cs
Web Access
Project: src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;
using Internal.IL.Stubs;
using Internal.IL;

using Debug = System.Diagnostics.Debug;
using ILLocalVariable = Internal.IL.Stubs.ILLocalVariable;
using Internal.TypeSystem.Ecma;
using System.Reflection.Metadata;

namespace Internal.TypeSystem.Interop
{
    internal partial class Marshaller
    {
        protected static Marshaller CreateMarshaller(MarshallerKind kind)
        {
            switch (kind)
            {
                case MarshallerKind.Enum:
                case MarshallerKind.BlittableValue:
                case MarshallerKind.BlittableStruct:
                case MarshallerKind.UnicodeChar:
                    return new BlittableValueMarshaller();
                case MarshallerKind.BlittableStructPtr:
                    return new BlittableStructPtrMarshaller();
                case MarshallerKind.AnsiChar:
                    return new AnsiCharMarshaller();
                case MarshallerKind.Array:
                    return new ArrayMarshaller();
                case MarshallerKind.BlittableArray:
                    return new BlittableArrayMarshaller();
                case MarshallerKind.Bool:
                case MarshallerKind.CBool:
                    return new BooleanMarshaller();
                case MarshallerKind.VariantBool:
                    return new BooleanMarshaller((short)-1);
                case MarshallerKind.AnsiString:
                    return new AnsiStringMarshaller();
                case MarshallerKind.UTF8String:
                    return new UTF8StringMarshaller();
                case MarshallerKind.UnicodeString:
                    return new UnicodeStringMarshaller();
                case MarshallerKind.AnsiBSTRString:
                    return new AnsiBSTRStringMarshaller();
                case MarshallerKind.BSTRString:
                    return new BSTRStringMarshaller();
                case MarshallerKind.SafeHandle:
                    return new SafeHandleMarshaller();
                case MarshallerKind.UnicodeStringBuilder:
                    return new StringBuilderMarshaller(isAnsi: false);
                case MarshallerKind.AnsiStringBuilder:
                    return new StringBuilderMarshaller(isAnsi: true);
                case MarshallerKind.VoidReturn:
                    return new VoidReturnMarshaller();
                case MarshallerKind.FunctionPointer:
                    return new DelegateMarshaller();
                case MarshallerKind.Struct:
                case MarshallerKind.Decimal:
                    return new StructMarshaller();
                case MarshallerKind.ByValAnsiString:
                    return new ByValAnsiStringMarshaller();
                case MarshallerKind.ByValUnicodeString:
                    return new ByValUnicodeStringMarshaller();
                case MarshallerKind.ByValAnsiCharArray:
                case MarshallerKind.ByValArray:
                    return new ByValArrayMarshaller();
                case MarshallerKind.AnsiCharArray:
                    return new AnsiCharArrayMarshaller();
                case MarshallerKind.HandleRef:
                    return new HandleRefMarshaller();
                case MarshallerKind.LayoutClass:
                    return new LayoutClassMarshaler();
                case MarshallerKind.LayoutClassPtr:
                    return new LayoutClassPtrMarshaller();
                case MarshallerKind.AsAnyA:
                    return new AsAnyMarshaller(isAnsi: true);
                case MarshallerKind.AsAnyW:
                    return new AsAnyMarshaller(isAnsi: false);
                case MarshallerKind.ComInterface:
                    return new ComInterfaceMarshaller();
                case MarshallerKind.OleDateTime:
                    return new OleDateTimeMarshaller();
                case MarshallerKind.OleCurrency:
                    return new OleCurrencyMarshaller();
                case MarshallerKind.FailedTypeLoad:
                    return new FailedTypeLoadMarshaller();
                case MarshallerKind.Variant:
                    return new VariantMarshaller();
                case MarshallerKind.CustomMarshaler:
                    return new CustomTypeMarshaller();
                case MarshallerKind.BlittableValueClassByRefReturn:
                    return new BlittableValueClassByRefReturn();

                default:
                    // ensures we don't throw during create marshaller. We will throw NSE
                    // during EmitIL which will be handled and an Exception method body
                    // will be emitted.
                    return new NotSupportedMarshaller();
            }
        }
     }

    internal sealed class AnsiCharArrayMarshaller : ArrayMarshaller
    {
        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "AllocMemoryForAnsiCharArray"u8);
            LoadManagedValue(codeStream);

            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreNativeValue(codeStream);
        }


        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "WideCharArrayToAnsiCharArray"u8);

            LoadManagedValue(codeStream);
            LoadNativeValue(codeStream);
            codeStream.Emit(PInvokeFlags.BestFitMapping ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
            codeStream.Emit(PInvokeFlags.ThrowOnUnmappableChar ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "AnsiCharArrayToWideCharArray"u8);

            LoadNativeValue(codeStream);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }
    }

    internal sealed class AnsiCharMarshaller : Marshaller
    {
        protected override void AllocAndTransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "WideCharToAnsiChar"u8);

            LoadManagedValue(codeStream);
            codeStream.Emit(PInvokeFlags.BestFitMapping ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
            codeStream.Emit(PInvokeFlags.ThrowOnUnmappableChar ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "AnsiCharToWideChar"u8);

            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
            StoreManagedValue(codeStream);
        }
    }

    internal sealed class StringBuilderMarshaller : Marshaller
    {
        private bool _isAnsi;
        public StringBuilderMarshaller(bool isAnsi)
        {
            _isAnsi = isAnsi;
        }

        internal override bool CleanupRequired
        {
            get
            {
                return true;
            }
        }

        internal override void EmitElementCleanup(ILCodeStream codeStream, ILEmitter emitter)
        {
            codeStream.Emit(ILOpcode.call, emitter.NewToken(
                                InteropTypes.GetMarshal(Context).GetKnownMethod("FreeCoTaskMem"u8, null)));
        }

        protected override void AllocNativeToManaged(ILCodeStream codeStream)
        {
            var emitter = _ilCodeStreams.Emitter;
            var lNull = emitter.NewCodeLabel();

            // Check for null
            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            codeStream.Emit(ILOpcode.newobj, emitter.NewToken(
                ManagedType.GetParameterlessConstructor()));
            StoreManagedValue(codeStream);
            codeStream.EmitLabel(lNull);
        }

        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ReadOnlySpan<byte> helperMethodName = _isAnsi ? "AllocMemoryForAnsiStringBuilder"u8 : "AllocMemoryForUnicodeStringBuilder"u8;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, helperMethodName);
            LoadManagedValue(codeStream);

            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreNativeValue(codeStream);
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ReadOnlySpan<byte> helperMethodName = _isAnsi ? "StringBuilderToAnsiString"u8 : "StringBuilderToUnicodeString"u8;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, helperMethodName);

            LoadManagedValue(codeStream);
            LoadNativeValue(codeStream);
            if (_isAnsi)
            {
                codeStream.Emit(PInvokeFlags.BestFitMapping ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
                codeStream.Emit(PInvokeFlags.ThrowOnUnmappableChar ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
            }
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ReadOnlySpan<byte> helperMethodName = _isAnsi ? "AnsiStringToStringBuilder"u8 : "UnicodeStringToStringBuilder"u8;
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, helperMethodName);
            LoadNativeValue(codeStream);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(helper));
        }


        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(
                                InteropTypes.GetMarshal(Context).GetKnownMethod("FreeCoTaskMem"u8, null)));
        }
    }

    internal sealed class HandleRefMarshaller : Marshaller
    {
        protected override void AllocAndTransformManagedToNative(ILCodeStream codeStream)
        {
            LoadManagedAddr(codeStream);
            codeStream.Emit(ILOpcode.ldfld, _ilCodeStreams.Emitter.NewToken(InteropTypes.GetHandleRef(Context).GetKnownField("_handle"u8)));
            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ThrowHelper.ThrowMarshalDirectiveException();
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ThrowHelper.ThrowMarshalDirectiveException();
        }

        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            LoadManagedAddr(codeStream);
            codeStream.Emit(ILOpcode.ldfld, _ilCodeStreams.Emitter.NewToken(InteropTypes.GetHandleRef(Context).GetKnownField("_wrapper"u8)));
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(InteropTypes.GetGC(Context).GetKnownMethod("KeepAlive"u8, null)));
        }
    }

    internal sealed class StructMarshaller : Marshaller
    {
        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.initobj, _ilCodeStreams.Emitter.NewToken(NativeType));
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            LoadManagedAddr(codeStream);
            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingManagedToNativeThunk(ManagedType)));
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            LoadNativeAddr(codeStream);
            LoadManagedAddr(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingNativeToManagedThunk(ManagedType)));
        }

        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            // Only do cleanup if it is IN
            if (!In)
            {
                return;
            }

            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingCleanupThunk(ManagedType)));
        }
    }

    internal class ByValArrayMarshaller : ArrayMarshaller
    {
        protected FieldDesc _managedField;
        protected FieldDesc _nativeField;

        public void EmitMarshallingIL(PInvokeILCodeStreams codeStreams, FieldDesc managedField, FieldDesc nativeField)
        {
            _managedField = managedField;
            _nativeField = nativeField;
            EmitMarshallingIL(codeStreams);
        }

        protected override void EmitElementCount(ILCodeStream codeStream, MarshalDirection direction)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            if (MarshalAsDescriptor == null || !MarshalAsDescriptor.SizeConst.HasValue)
            {
                throw new InvalidProgramException("SizeConst is required for ByValArray.");
            }

            if (direction == MarshalDirection.Forward)
            {
                // In forward direction ElementCount = Min(managed.length, SizeConst);

                var vLength = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
                var lSmaller = emitter.NewCodeLabel();
                var lDone = emitter.NewCodeLabel();

                codeStream.EmitLdArg(0);
                codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(_managedField));

                var lNullCheck = emitter.NewCodeLabel();
                codeStream.Emit(ILOpcode.brfalse, lNullCheck);

                codeStream.EmitLdArg(0);
                codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(_managedField));

                codeStream.Emit(ILOpcode.ldlen);
                codeStream.Emit(ILOpcode.conv_i4);
                codeStream.EmitStLoc(vLength);

                codeStream.EmitLabel(lNullCheck);

                Debug.Assert(MarshalAsDescriptor.SizeConst.HasValue);
                int sizeConst = (int)MarshalAsDescriptor.SizeConst.Value;

                codeStream.EmitLdc(sizeConst);
                codeStream.EmitLdLoc(vLength);
                codeStream.Emit(ILOpcode.blt, lSmaller);

                codeStream.EmitLdLoc(vLength);
                codeStream.Emit(ILOpcode.br, lDone);

                codeStream.EmitLabel(lSmaller);
                codeStream.EmitLdc(sizeConst);

                codeStream.EmitLabel(lDone);
            }
            else
            {
                // In reverse direction ElementCount = SizeConst;
                Debug.Assert(MarshalAsDescriptor.SizeConst.HasValue);
                int sizeConst = (int)MarshalAsDescriptor.SizeConst.Value;

                codeStream.EmitLdc(sizeConst);
            }
        }

        protected override void EmitMarshalFieldManagedToNative()
        {

            // It generates the following code
            //if (ManagedArg.Field != null)
            //{
            //
            //  fixed (InlineArray* pUnsafe = &NativeArg.Field)
            //  {
            //        uint index = 0u;
            //        while ((ulong)index < (ulong)((long)ManagedArg.Field.Length))
            //        {
            //            NativeArg.s[index] = ManagedArg.Field[(int)index];
            //            index += 1u;
            //        }
            //  }
            //}

            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeStream codeStream = _ilCodeStreams.MarshallingCodeStream;
            var nativeArrayType = NativeType as InlineArrayType;
            Debug.Assert(nativeArrayType != null);
            Debug.Assert(ManagedType is ArrayType);

            var managedElementType = ((ArrayType)ManagedType).ElementType;

            ILCodeLabel lDone = emitter.NewCodeLabel();
            ILCodeLabel lRangeCheck = emitter.NewCodeLabel();
            ILCodeLabel lLoopHeader = emitter.NewCodeLabel();

            ILLocalVariable vIndex = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
            ILLocalVariable vLength = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
            ILLocalVariable vNative = emitter.NewLocal(NativeType.MakeByRefType(), isPinned: true);

            // check if ManagedType == null, then return
            codeStream.EmitLdArg(0);
            codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(_managedField));
            codeStream.Emit(ILOpcode.brfalse, lDone);

            codeStream.EmitLdArg(1);
            codeStream.Emit(ILOpcode.ldflda, emitter.NewToken(_nativeField));
            codeStream.EmitStLoc(vNative);

            EmitElementCount(codeStream, MarshalDirection.Forward);
            codeStream.EmitStLoc(vLength);

            codeStream.EmitLdc(0);
            codeStream.EmitStLoc(vIndex);
            codeStream.Emit(ILOpcode.br, lRangeCheck);

            codeStream.EmitLabel(lLoopHeader);
            codeStream.EmitLdArg(1);
            codeStream.Emit(ILOpcode.ldflda, emitter.NewToken(_nativeField));
            codeStream.EmitLdLoc(vIndex);

            codeStream.EmitLdArg(0);
            codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(_managedField));
            codeStream.EmitLdLoc(vIndex);

            codeStream.EmitLdElem(managedElementType);

            // generate marshalling IL for the element
            GetElementMarshaller(MarshalDirection.Forward)
                .EmitMarshallingIL(new PInvokeILCodeStreams(_ilCodeStreams.Emitter, codeStream));

            codeStream.Emit(ILOpcode.call, emitter.NewToken(
                           nativeArrayType.GetInlineArrayMethod(InlineArrayMethodKind.Setter)));

            codeStream.EmitLdLoc(vIndex);
            codeStream.EmitLdc(1);
            codeStream.Emit(ILOpcode.add);
            codeStream.EmitStLoc(vIndex);

            codeStream.EmitLabel(lRangeCheck);
            codeStream.EmitLdLoc(vIndex);

            codeStream.EmitLdLoc(vLength);
            codeStream.Emit(ILOpcode.blt, lLoopHeader);

            codeStream.EmitLabel(lDone);
        }

        protected override void EmitMarshalFieldNativeToManaged()
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeStream codeStream = _ilCodeStreams.UnmarshallingCodestream;

            // It generates the following IL:
            //  ManagedArg.s = new ElementType[Length];
            //
            //    for (uint index = 0u; index < Length; index += 1u)
            //    {
            //        ManagedArg.s[index] = NativeArg.s[index];
            //    }
            //

            ILCodeLabel lRangeCheck = emitter.NewCodeLabel();
            ILCodeLabel lLoopHeader = emitter.NewCodeLabel();

            Debug.Assert(ManagedType is ArrayType);

            var nativeArrayType = NativeType as InlineArrayType;
            Debug.Assert(nativeArrayType != null);

            var managedElementType = ((ArrayType)ManagedType).ElementType;

            ILLocalVariable vLength = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));
            codeStream.EmitLdArg(1);
            // load the length
            EmitElementCount(codeStream, MarshalDirection.Reverse);
            codeStream.EmitStLoc(vLength);

            codeStream.EmitLdLoc(vLength);
            codeStream.Emit(ILOpcode.newarr, emitter.NewToken(managedElementType));
            codeStream.Emit(ILOpcode.stfld, emitter.NewToken(_managedField));


            var vIndex = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));

            // index = 0
            codeStream.EmitLdc(0);
            codeStream.EmitStLoc(vIndex);
            codeStream.Emit(ILOpcode.br, lRangeCheck);

            codeStream.EmitLabel(lLoopHeader);

            // load managed type
            codeStream.EmitLdArg(1);
            codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(_managedField));

            codeStream.EmitLdLoc(vIndex);

            // load native type
            codeStream.EmitLdArg(0);
            codeStream.Emit(ILOpcode.ldflda, emitter.NewToken(_nativeField));
            codeStream.EmitLdLoc(vIndex);

            codeStream.Emit(ILOpcode.call, emitter.NewToken(
               nativeArrayType.GetInlineArrayMethod(InlineArrayMethodKind.Getter)));

            // generate marshalling IL for the element
            GetElementMarshaller(MarshalDirection.Reverse)
                .EmitMarshallingIL(new PInvokeILCodeStreams(_ilCodeStreams.Emitter, codeStream));

            codeStream.EmitStElem(managedElementType);

            codeStream.EmitLdLoc(vIndex);
            codeStream.EmitLdc(1);
            codeStream.Emit(ILOpcode.add);
            codeStream.EmitStLoc(vIndex);

            codeStream.EmitLabel(lRangeCheck);

            codeStream.EmitLdLoc(vIndex);
            codeStream.EmitLdLoc(vLength);
            codeStream.Emit(ILOpcode.blt, lLoopHeader);
        }
    }

    internal abstract class ByValStringMarshaller : ByValArrayMarshaller
    {
        protected override void EmitElementCount(ILCodeStream codeStream, MarshalDirection direction)
        {
            if (MarshalAsDescriptor == null || !MarshalAsDescriptor.SizeConst.HasValue)
            {
                throw new InvalidProgramException("SizeConst is required for ByValString.");
            }
            codeStream.EmitLdc((int)MarshalAsDescriptor.SizeConst.Value);
        }

        protected abstract bool IsAnsi
        {
            get;
        }

        protected abstract MethodDesc GetManagedToNativeHelper();

        protected abstract MethodDesc GetNativeToManagedHelper();

        protected override void EmitMarshalFieldManagedToNative()
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeStream codeStream = _ilCodeStreams.MarshallingCodeStream;

            var nativeArrayType = NativeType as InlineArrayType;
            Debug.Assert(nativeArrayType != null);

            codeStream.EmitLdArg(0);
            codeStream.Emit(ILOpcode.ldfld, emitter.NewToken(_managedField));

            codeStream.EmitLdArg(1);
            codeStream.Emit(ILOpcode.ldflda, emitter.NewToken(_nativeField));
            codeStream.Emit(ILOpcode.conv_u);

            EmitElementCount(codeStream, MarshalDirection.Forward);

            if (IsAnsi)
            {
                codeStream.Emit(PInvokeFlags.BestFitMapping ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
                codeStream.Emit(PInvokeFlags.ThrowOnUnmappableChar ? ILOpcode.ldc_i4_1 : ILOpcode.ldc_i4_0);
            }

            codeStream.Emit(ILOpcode.call, emitter.NewToken(GetManagedToNativeHelper()));
        }

        protected override void EmitMarshalFieldNativeToManaged()
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeStream codeStream = _ilCodeStreams.UnmarshallingCodestream;

            codeStream.EmitLdArg(1);

            codeStream.EmitLdArg(0);
            codeStream.Emit(ILOpcode.ldflda, emitter.NewToken(_nativeField));
            codeStream.Emit(ILOpcode.conv_u);

            EmitElementCount(codeStream, MarshalDirection.Reverse);

            codeStream.Emit(ILOpcode.call, emitter.NewToken(GetNativeToManagedHelper()));

            codeStream.Emit(ILOpcode.stfld, emitter.NewToken(_managedField));
        }
    }

    internal sealed class ByValAnsiStringMarshaller : ByValStringMarshaller
    {
        protected override bool IsAnsi
        {
            get
            {
                return true;
            }
        }

        protected override MethodDesc GetManagedToNativeHelper()
        {
            return Context.GetHelperEntryPoint("InteropHelpers"u8, "StringToByValAnsiString"u8);
        }

        protected override MethodDesc GetNativeToManagedHelper()
        {
            return Context.GetHelperEntryPoint("InteropHelpers"u8, "ByValAnsiStringToString"u8);
        }
    }

    internal sealed class ByValUnicodeStringMarshaller : ByValStringMarshaller
    {
        protected override bool IsAnsi
        {
            get
            {
                return false;
            }
        }

        protected override MethodDesc GetManagedToNativeHelper()
        {
            return Context.GetHelperEntryPoint("InteropHelpers"u8, "StringToUnicodeFixedArray"u8);
        }

        protected override MethodDesc GetNativeToManagedHelper()
        {
            return Context.GetHelperEntryPoint("InteropHelpers"u8, "UnicodeToStringFixedArray"u8);
        }
    }

    internal sealed class LayoutClassMarshaler : Marshaller
    {
        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.initobj, _ilCodeStreams.Emitter.NewToken(NativeType));
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadManagedValue(codeStream);
            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingManagedToNativeThunk(ManagedType)));

            codeStream.EmitLabel(lNull);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNonNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brtrue, lNonNull);

            codeStream.Emit(ILOpcode.ldtoken, emitter.NewToken(ManagedType));
            codeStream.Emit(ILOpcode.call, emitter.NewToken(InteropTypes.GetType(Context).GetMethod("GetTypeFromHandle"u8, null)));
            codeStream.Emit(ILOpcode.call, emitter.NewToken(InteropTypes.GetRuntimeHelpers(Context).GetKnownMethod("GetUninitializedObject"u8, null)));
            StoreManagedValue(codeStream);

            codeStream.EmitLabel(lNonNull);
            LoadNativeAddr(codeStream);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingNativeToManagedThunk(ManagedType)));
        }

        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            // Only do cleanup if it is IN
            if (!In)
            {
                return;
            }

            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingCleanupThunk(ManagedType)));
        }
    }

    internal sealed class LayoutClassPtrMarshaller : Marshaller
    {
        // Blittable layout classes passed by value CLR->native are marshalled by pinning the managed
        // object and passing an interior pointer to its data directly to native code, mirroring CoreCLR's
        // ILBlittablePtrMarshaller. This preserves pointer identity and gives implicit [In,Out] semantics
        // regardless of [In]/[Out]. Byref, reverse, return, field, and non-blittable scenarios instead copy
        // through the struct marshalling thunks.
        private bool MarshalViaPinning =>
            MarshalDirection == MarshalDirection.Forward
            && !IsManagedByRef
            && !Return
            && MarshallerType == MarshallerType.Argument
            && MarshalUtils.IsBlittableType(ManagedType);

        protected override void AllocAndTransformManagedToNative(ILCodeStream codeStream)
        {
            if (!MarshalViaPinning)
            {
                base.AllocAndTransformManagedToNative(codeStream);
                return;
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            // Default the native value to null so a null managed reference marshals as a null pointer.
            codeStream.EmitLdc(0);
            codeStream.Emit(ILOpcode.conv_i);
            StoreNativeValue(codeStream);

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            // Pin the object and pass the address of its first field (the object's data) to native code.
            ILLocalVariable vPinnedObject = emitter.NewLocal(ManagedType, isPinned: true);
            LoadManagedValue(codeStream);
            codeStream.EmitStLoc(vPinnedObject);

            FieldDesc rawDataField = Context.SystemModule
                .GetKnownType("System.Runtime.CompilerServices"u8, "RawData"u8)
                .GetKnownField("Data"u8);

            codeStream.EmitLdLoc(vPinnedObject);
            codeStream.Emit(ILOpcode.ldflda, emitter.NewToken(rawDataField));
            codeStream.Emit(ILOpcode.conv_i);
            StoreNativeValue(codeStream);

            codeStream.EmitLabel(lNull);
        }

        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            codeStream.EmitLdc(0);
            codeStream.Emit(ILOpcode.conv_i);
            StoreNativeValue(codeStream);

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            TypeDesc nativeStructType = InteropStateManager.GetStructMarshallingNativeType(ManagedType);

            ILLocalVariable lNativeType = emitter.NewLocal(nativeStructType);
            codeStream.EmitLdLoca(lNativeType);
            codeStream.Emit(ILOpcode.initobj, emitter.NewToken(nativeStructType));
            codeStream.EmitLdLoca(lNativeType);
            StoreNativeValue(codeStream);

            codeStream.EmitLabel(lNull);
        }

        protected override void AllocNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            codeStream.Emit(ILOpcode.ldtoken, emitter.NewToken(ManagedType));
            codeStream.Emit(ILOpcode.call, emitter.NewToken(InteropTypes.GetType(Context).GetMethod("GetTypeFromHandle"u8, null)));
            codeStream.Emit(ILOpcode.call, emitter.NewToken(InteropTypes.GetRuntimeHelpers(Context).GetKnownMethod("GetUninitializedObject"u8, null)));
            StoreManagedValue(codeStream);
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadManagedValue(codeStream);
            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingManagedToNativeThunk(ManagedType)));

            codeStream.EmitLabel(lNull);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            // When marshalling via pinning the native code operates directly on the managed object's
            // memory, so there is nothing to copy back.
            if (MarshalViaPinning)
            {
                return;
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadNativeValue(codeStream);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                InteropStateManager.GetStructMarshallingNativeToManagedThunk(ManagedType)));

            codeStream.EmitLabel(lNull);
        }

        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            // Pinning does not allocate any native resources that require cleanup.
            if (MarshalViaPinning)
            {
                return;
            }

            // Only do cleanup if it is IN
            if (!In)
            {
                return;
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(
                InteropStateManager.GetStructMarshallingCleanupThunk(ManagedType)));

            codeStream.EmitLabel(lNull);
        }
    }

    internal sealed class AsAnyMarshaller : Marshaller
    {
        // This flag affects encoding of string, StringBuilder and Char array marshalling.
        // It does not affect LayoutClass marshalling.
        // Note that the CoreLib portion of the marshaller currently only supports LayoutClass.
        private readonly bool _isAnsi;

        public AsAnyMarshaller(bool isAnsi)
        {
            _isAnsi = isAnsi;
        }

        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();
            ILLocalVariable lSize = emitter.NewLocal(Context.GetWellKnownType(WellKnownType.Int32));

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            MethodDesc getNativeSizeHelper = Context.GetHelperEntryPoint("InteropHelpers"u8, "AsAnyGetNativeSize"u8);

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(getNativeSizeHelper));
            codeStream.Emit(ILOpcode.dup);
            codeStream.EmitStLoc(lSize);
            codeStream.Emit(ILOpcode.localloc);
            codeStream.Emit(ILOpcode.dup);
            StoreNativeValue(codeStream);
            codeStream.EmitLdc(0);
            codeStream.EmitLdLoc(lSize);
            codeStream.Emit(ILOpcode.initblk);

            codeStream.EmitLabel(lNull);
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadManagedValue(codeStream);
            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                Context.GetHelperEntryPoint("InteropHelpers"u8, "AsAnyMarshalManagedToNative"u8)));

            codeStream.EmitLabel(lNull);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadNativeValue(codeStream);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, _ilCodeStreams.Emitter.NewToken(
                Context.GetHelperEntryPoint("InteropHelpers"u8, "AsAnyMarshalNativeToManaged"u8)));

            codeStream.EmitLabel(lNull);
        }

        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            // Only do cleanup if it is IN
            if (!In)
            {
                return;
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;
            ILCodeLabel lNull = emitter.NewCodeLabel();

            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.brfalse, lNull);

            LoadNativeValue(codeStream);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(
                Context.GetHelperEntryPoint("InteropHelpers"u8, "AsAnyCleanupNative"u8)));

            codeStream.EmitLabel(lNull);
        }
    }

    internal sealed class ComInterfaceMarshaller : Marshaller
    {
        protected override void AllocAndTransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;

            LoadManagedValue(codeStream);
            var parameterType = this.ManagedParameterType;
            if (parameterType.IsByRef)
            {
                parameterType = ((ByRefType)this.ManagedParameterType).ParameterType;
            }

            CustomAttributeValue<TypeDesc>? guidAttributeValue = (parameterType as EcmaType)?
                .GetDecodedCustomAttribute("System.Runtime.InteropServices", "GuidAttribute");
            if (guidAttributeValue != null)
            {
                var guidValue = (string)guidAttributeValue.Value.FixedArguments[0].Value;
                Span<byte> bytes = Guid.Parse(guidValue).ToByteArray();
                codeStream.EmitLdc(BinaryPrimitives.ReadInt32LittleEndian(bytes));
                codeStream.EmitLdc(BinaryPrimitives.ReadInt16LittleEndian(bytes.Slice(4)));
                codeStream.EmitLdc(BinaryPrimitives.ReadInt16LittleEndian(bytes.Slice(6)));
                for (int i = 8; i < 16; i++)
                    codeStream.EmitLdc(bytes[i]);

                MetadataType guidType = Context.SystemModule.GetKnownType("System"u8, "Guid"u8);
                var int32Type = Context.GetWellKnownType(WellKnownType.Int32);
                var int16Type = Context.GetWellKnownType(WellKnownType.Int16);
                var byteType = Context.GetWellKnownType(WellKnownType.Byte);
                var sig = new MethodSignature(
                    MethodSignatureFlags.None,
                    genericParameterCount: 0,
                    returnType: Context.GetWellKnownType(WellKnownType.Void),
                    parameters: new TypeDesc[] { int32Type, int16Type, int16Type, byteType, byteType, byteType, byteType, byteType, byteType, byteType, byteType });
                MethodDesc guidCtorHandleMethod =
                    guidType.GetKnownMethod(".ctor"u8, sig);
                codeStream.Emit(ILOpcode.newobj, emitter.NewToken(guidCtorHandleMethod));

                MethodDesc helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "ConvertManagedComInterfaceToNative"u8);
                codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
            }
            else
            {
                if (!parameterType.IsObject)
                {
                    throw new NotSupportedException();
                }

                MethodDesc helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "ConvertManagedComInterfaceToIUnknown"u8);
                codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
            }

            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;

            LoadNativeValue(codeStream);
            MethodDesc helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "ConvertNativeComInterfaceToManaged"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreManagedValue(codeStream);
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            throw new NotSupportedException();
        }
    }

    internal sealed class AnsiBSTRStringMarshaller : Marshaller
    {

        internal override bool CleanupRequired => true;

        internal override void EmitElementCleanup(ILCodeStream codeStream, ILEmitter emitter)
        {
            var helper = InteropTypes.GetMarshal(Context).GetKnownMethod("FreeBSTR"u8, null);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }
        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadManagedValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "StringToAnsiBstrBuffer"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadNativeValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "AnsiBstrBufferToString"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreManagedValue(codeStream);
        }
    }

    internal sealed class BSTRStringMarshaller : Marshaller
    {

        internal override bool CleanupRequired => true;

        internal override void EmitElementCleanup(ILCodeStream codeStream, ILEmitter emitter)
        {
            var helper = InteropTypes.GetMarshal(Context).GetKnownMethod("FreeBSTR"u8, null);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }
        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadManagedValue(codeStream);

            var helper = InteropTypes.GetMarshal(Context).GetKnownMethod("StringToBSTR"u8, null);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadNativeValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "BstrBufferToString"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreManagedValue(codeStream);
        }
    }

    internal sealed class OleDateTimeMarshaller : Marshaller
    {
        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadManagedValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "DateTimeToOleDateTime"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadNativeValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "OleDateTimeToDateTime"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreManagedValue(codeStream);
        }
    }

    internal sealed class OleCurrencyMarshaller : Marshaller
    {
        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadManagedValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "DecimalToOleCurrency"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreNativeValue(codeStream);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;
            LoadNativeValue(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "OleCurrencyToDecimal"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreManagedValue(codeStream);
        }
    }

    internal sealed class FailedTypeLoadMarshaller : Marshaller
    {
        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            ThrowHelper.ThrowTypeLoadException(ManagedType);
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            ThrowHelper.ThrowTypeLoadException(ManagedType);
        }
    }

    internal sealed class VariantMarshaller : Marshaller
    {
        protected override void AllocManagedToNative(ILCodeStream codeStream)
        {
            LoadNativeAddr(codeStream);
            codeStream.Emit(ILOpcode.initobj, _ilCodeStreams.Emitter.NewToken(NativeType));
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            if (this.MarshalDirection == MarshalDirection.Reverse)
            {
                throw new NotSupportedException();
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;

            LoadManagedValue(codeStream);
            LoadNativeAddr(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "ConvertObjectToVariant"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            if (this.MarshalDirection == MarshalDirection.Reverse)
            {
                throw new NotSupportedException();
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;

            LoadNativeAddr(codeStream);

            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "VariantToObject"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));

            StoreManagedValue(codeStream);
        }

        protected override void EmitCleanupManaged(ILCodeStream codeStream)
        {
            // Only do cleanup if it is IN
            if (!In)
            {
                return;
            }

            ILEmitter emitter = _ilCodeStreams.Emitter;

            LoadNativeAddr(codeStream);
            var helper = Context.GetHelperEntryPoint("InteropHelpers"u8, "CleanupVariant"u8);
            codeStream.Emit(ILOpcode.call, emitter.NewToken(helper));
        }
    }
#pragma warning disable CA1852
    internal class CustomTypeMarshaller : Marshaller
#pragma warning restore CA1852
    {
        private ILLocalVariable MarshallerLocalVariable = (ILLocalVariable)(-1);

        private ILLocalVariable InitializeMarshallerVariable()
        {
            if (MarshallerLocalVariable != (ILLocalVariable)(-1))
            {
                return MarshallerLocalVariable;
            }

            var marshallerType = MarshalAsDescriptor.MarshallerType;
            if (marshallerType.IsGenericDefinition)
            {
                ThrowHelper.ThrowTypeLoadException(marshallerType);
            }

            var customMarshallerType = Context.SystemModule.GetKnownType("System.Runtime.InteropServices"u8, "ICustomMarshaler"u8);
            var getInstanceMethod = marshallerType.GetMethod(
                "GetInstance"u8,
                new MethodSignature(MethodSignatureFlags.Static, 0, customMarshallerType, [ Context.GetWellKnownType(WellKnownType.String) ]));
            if (ManagedType.IsValueType || ManagedType.IsPointer || ManagedType.IsFunctionPointer)
            {
                ThrowHelper.ThrowMarshalDirectiveException();
            }

            var initializeCustomMarshallerMethod = Context.GetHelperEntryPoint("InteropHelpers"u8, "InitializeCustomMarshaller"u8);

            ILEmitter emitter = _ilCodeStreams.Emitter;
            MarshallerLocalVariable = emitter.NewLocal(customMarshallerType);
            var cookie = MarshalAsDescriptor.Cookie;

            // Custom marshaller initialization should not be caught, so initialize early
            ILCodeStream fnptrLoadStream = _ilCodeStreams.FunctionPointerLoadStream;
            fnptrLoadStream.Emit(ILOpcode.ldtoken, emitter.NewToken(ManagedType));
            fnptrLoadStream.Emit(ILOpcode.ldtoken, emitter.NewToken(marshallerType));
            fnptrLoadStream.Emit(ILOpcode.ldstr, emitter.NewToken(cookie));
            if (getInstanceMethod != null)
            {
                fnptrLoadStream.Emit(ILOpcode.ldftn, emitter.NewToken(getInstanceMethod));
            }
            else
            {
                fnptrLoadStream.EmitLdc(0);
                fnptrLoadStream.Emit(ILOpcode.conv_i);
            }

            fnptrLoadStream.Emit(ILOpcode.call, emitter.NewToken(initializeCustomMarshallerMethod));
            fnptrLoadStream.EmitStLoc(MarshallerLocalVariable);
            return MarshallerLocalVariable;
        }

        protected override void TransformManagedToNative(ILCodeStream codeStream)
        {
            var lMarshaller = InitializeMarshallerVariable();

            var customMarshallerType = Context.SystemModule.GetKnownType("System.Runtime.InteropServices"u8, "ICustomMarshaler"u8);
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var manageToNativeMethod = customMarshallerType.GetKnownMethod(
                "MarshalManagedToNative"u8,
                new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.IntPtr), [ Context.GetWellKnownType(WellKnownType.Object) ]));

            codeStream.EmitLdLoc(lMarshaller);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.callvirt, emitter.NewToken(manageToNativeMethod));
            StoreNativeValue(codeStream);
            if (MarshalDirection == MarshalDirection.Forward)
            {
                if (In && Out)
                {
                    EmitCleanUpManagedData(codeStream);
                }

                EmitCleanUpNativeData(_ilCodeStreams.CleanupCodeStream);
            }
            else
            {
                EmitCleanUpManagedData(codeStream);
            }
        }

        protected override void TransformNativeToManaged(ILCodeStream codeStream)
        {
            var lMarshaller = InitializeMarshallerVariable();

            var customMarshallerType = Context.SystemModule.GetKnownType("System.Runtime.InteropServices"u8, "ICustomMarshaler"u8);
            ILEmitter emitter = _ilCodeStreams.Emitter;
            var marshalNativeToManagedMethod = customMarshallerType.GetKnownMethod(
                "MarshalNativeToManaged"u8,
                new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Object), [ Context.GetWellKnownType(WellKnownType.IntPtr) ]));

            codeStream.EmitLdLoc(lMarshaller);
            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.callvirt, emitter.NewToken(marshalNativeToManagedMethod));
            StoreManagedValue(codeStream);
        }

        protected void EmitCleanUpManagedData(ILCodeStream codeStream)
        {
            var lMarshaller = InitializeMarshallerVariable();

            var customMarshallerType = Context.SystemModule.GetKnownType("System.Runtime.InteropServices"u8, "ICustomMarshaler"u8);
            ILEmitter emitter = _ilCodeStreams.Emitter;

            // Call CleanUpManagedData on cleanup code stream.
            var cleanupManagedDataMethod = customMarshallerType.GetKnownMethod(
                "CleanUpManagedData"u8,
                new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Void), [ Context.GetWellKnownType(WellKnownType.Object) ]));

            codeStream.EmitLdLoc(lMarshaller);
            LoadManagedValue(codeStream);
            codeStream.Emit(ILOpcode.callvirt, emitter.NewToken(cleanupManagedDataMethod));
        }

        protected void EmitCleanUpNativeData(ILCodeStream codeStream)
        {
            var lMarshaller = InitializeMarshallerVariable();

            var customMarshallerType = Context.SystemModule.GetKnownType("System.Runtime.InteropServices"u8, "ICustomMarshaler"u8);
            ILEmitter emitter = _ilCodeStreams.Emitter;

            // Call CleanUpNativeData on cleanup code stream.
            var cleanupNativeDataMethod = customMarshallerType.GetKnownMethod(
                "CleanUpNativeData"u8,
                new MethodSignature(MethodSignatureFlags.None, 0, Context.GetWellKnownType(WellKnownType.Void), [ Context.GetWellKnownType(WellKnownType.IntPtr) ]));

            codeStream.EmitLdLoc(lMarshaller);
            LoadNativeValue(codeStream);
            codeStream.Emit(ILOpcode.callvirt, emitter.NewToken(cleanupNativeDataMethod));
        }
    }

    internal sealed class BlittableValueClassByRefReturn : Marshaller
    {
        protected override void SetupArgumentsForReturnValueMarshalling()
        {
            ILEmitter emitter = _ilCodeStreams.Emitter;

            _managedHome = new Home(emitter.NewLocal(ManagedParameterType), ManagedParameterType, isByRef: false);
            _nativeHome = new Home(emitter.NewLocal(NativeType), NativeType, isByRef: false);
        }
    }
}