{%- if module.needs_ffi_status() %}
    [StructLayout(LayoutKind.Sequential)]
    internal struct FfiStatus
    {
        public int code;
    }

{% endif %}
{%- if module.needs_last_error() %}
    [StructLayout(LayoutKind.Sequential)]
    internal struct FfiString
    {
        public IntPtr ptr;
        public UIntPtr len;
        public UIntPtr cap;
    }

{% endif %}
{%- if module.needs_ffi_buf() %}
    [StructLayout(LayoutKind.Sequential)]
    internal struct FfiBuf
    {
        public IntPtr ptr;
        public UIntPtr len;
        public UIntPtr cap;
        public UIntPtr align;

        internal static unsafe FfiBuf FromBytes(byte[] bytes)
        {
            if (bytes.Length == 0)
            {
                return default;
            }

            void* allocated = NativeMemory.Alloc((nuint)bytes.Length);
            Marshal.Copy(bytes, 0, (IntPtr)allocated, bytes.Length);
            return new FfiBuf
            {
                ptr = (IntPtr)allocated,
                len = (UIntPtr)bytes.Length,
                cap = (UIntPtr)bytes.Length,
                align = (UIntPtr)1,
            };
        }
    }

{% endif %}
{%- if module.needs_wire_reader() %}
    internal sealed class WireReader
    {
        // Reads directly from the unmanaged FfiBuf pointer — no eager copy into
        // a managed byte[]. Safe APIs only: Marshal.Read* for primitives,
        // Marshal.PtrToStringUTF8 for length-prefixed UTF-8, Marshal.Copy for
        // bytes. BitConverter.IsLittleEndian is a JIT intrinsic that folds the
        // byte-swap branch away on little-endian hosts (all our supported
        // targets).
        private readonly IntPtr _ptr;
        private readonly int _length;
        private int _pos;

        internal WireReader(FfiBuf buf)
        {
            _ptr = buf.ptr;
            _length = buf.ptr == IntPtr.Zero ? 0 : checked((int)(nuint)buf.len);
            _pos = 0;
        }

        internal WireReader(IntPtr ptr, UIntPtr len)
        {
            _ptr = ptr;
            _length = ptr == IntPtr.Zero ? 0 : checked((int)(nuint)len);
            _pos = 0;
        }

        internal bool ReadBool() => ReadU8() != 0;

        internal sbyte ReadI8()
        {
            Require(1, "i8");
            sbyte v = (sbyte)Marshal.ReadByte(_ptr, _pos);
            _pos += 1;
            return v;
        }

        internal byte ReadU8()
        {
            Require(1, "u8");
            byte v = Marshal.ReadByte(_ptr, _pos);
            _pos += 1;
            return v;
        }

        internal short ReadI16()
        {
            Require(2, "i16");
            short v = Marshal.ReadInt16(_ptr, _pos);
            _pos += 2;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal ushort ReadU16()
        {
            Require(2, "u16");
            ushort v = (ushort)Marshal.ReadInt16(_ptr, _pos);
            _pos += 2;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal int ReadI32()
        {
            Require(4, "i32");
            int v = Marshal.ReadInt32(_ptr, _pos);
            _pos += 4;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal uint ReadU32()
        {
            Require(4, "u32");
            uint v = (uint)Marshal.ReadInt32(_ptr, _pos);
            _pos += 4;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal long ReadI64()
        {
            Require(8, "i64");
            long v = Marshal.ReadInt64(_ptr, _pos);
            _pos += 8;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal ulong ReadU64()
        {
            Require(8, "u64");
            ulong v = (ulong)Marshal.ReadInt64(_ptr, _pos);
            _pos += 8;
            return BitConverter.IsLittleEndian ? v : BinaryPrimitives.ReverseEndianness(v);
        }

        internal float ReadF32()
        {
            Require(4, "f32");
            int bits = Marshal.ReadInt32(_ptr, _pos);
            _pos += 4;
            return BitConverter.Int32BitsToSingle(BitConverter.IsLittleEndian ? bits : BinaryPrimitives.ReverseEndianness(bits));
        }

        internal double ReadF64()
        {
            Require(8, "f64");
            long bits = Marshal.ReadInt64(_ptr, _pos);
            _pos += 8;
            return BitConverter.Int64BitsToDouble(BitConverter.IsLittleEndian ? bits : BinaryPrimitives.ReverseEndianness(bits));
        }

        // usize/isize travel as 64-bit integers on the wire so the wire
        // layout stays stable across pointer widths.
        internal nint ReadNInt() => (nint)ReadI64();
        internal nuint ReadNUInt() => (nuint)ReadU64();

        // Built-in value types travel as (seconds, nanos) for Duration and
        // SystemTime, two i64s for UUID, length-prefixed UTF-8 for URL.
        // Mirrors Java's WireReader contract; the C# stdlib types
        // (TimeSpan, DateTime, Guid, Uri) carry 100ns precision so a Rust
        // Duration / SystemTime whose nanos isn't a multiple of 100 is
        // rounded toward zero on decode. All references go through
        // `global::System.` so a user contract that defines a same-named
        // record can't shadow these signatures. Each helper is gated on
        // the contract actually using that builtin so unused helpers
        // don't appear in the generated source.
{%- if module.uses_duration_builtin() %}
        internal global::System.TimeSpan ReadDuration()
        {
            long seconds = ReadI64();
            int nanos = ReadI32();
            if (seconds < 0L) throw new InvalidOperationException("corrupt wire: negative duration seconds");
            if (nanos < 0) throw new InvalidOperationException("corrupt wire: negative duration nanos");
            if (nanos >= 1_000_000_000) throw new InvalidOperationException("corrupt wire: duration nanos out of range");
            return new global::System.TimeSpan(checked(seconds * global::System.TimeSpan.TicksPerSecond + nanos / 100));
        }
{%- endif %}
{%- if module.uses_system_time_builtin() %}

        internal global::System.DateTime ReadDateTime()
        {
            long seconds = ReadI64();
            int nanos = ReadI32();
            if (nanos < 0) throw new InvalidOperationException("corrupt wire: negative date-time nanos");
            if (nanos >= 1_000_000_000) throw new InvalidOperationException("corrupt wire: date-time nanos out of range");
            return global::System.DateTime.UnixEpoch.AddTicks(checked(seconds * global::System.TimeSpan.TicksPerSecond + nanos / 100));
        }
{%- endif %}
{%- if module.uses_uuid_builtin() %}

        internal global::System.Guid ReadUuid()
        {
            long msb = ReadI64();
            long lsb = ReadI64();
            Span<byte> bytes = stackalloc byte[16];
            BinaryPrimitives.WriteInt64BigEndian(bytes[0..8], msb);
            BinaryPrimitives.WriteInt64BigEndian(bytes[8..16], lsb);
            return new global::System.Guid(bytes, bigEndian: true);
        }
{%- endif %}
{%- if module.uses_url_builtin() %}

        internal global::System.Uri ReadUri() => new global::System.Uri(ReadString());
{%- endif %}

        internal string ReadString()
        {
            int len = ReadI32();
            if (len == 0) return "";
            if (len < 0) throw new InvalidOperationException("corrupt wire: negative string length");
            Require(len, "string payload");
            string v = Marshal.PtrToStringUTF8(_ptr + _pos, len)
                ?? throw new InvalidOperationException("PtrToStringUTF8 returned null");
            _pos += len;
            return v;
        }

        internal byte[] ReadBytes()
        {
            int len = ReadI32();
            if (len == 0) return Array.Empty<byte>();
            if (len < 0) throw new InvalidOperationException("corrupt wire: negative bytes length");
            Require(len, "bytes payload");
            byte[] v = new byte[len];
            Marshal.Copy(_ptr + _pos, v, 0, len);
            _pos += len;
            return v;
        }

        /// Reads the remaining bytes of this buffer as a `T[]`, assuming
        /// the buffer holds a raw element array with no length prefix.
        /// This matches the wire shape of a top-level `Vec<T>` return,
        /// where the FfiBuf's own `len` provides the element count.
        internal T[] ReadBlittableArray<T>() where T : unmanaged
        {
            int byteCount = _length - _pos;
            if (byteCount < 0)
                throw new InvalidOperationException("corrupt wire: read position past end");
            int elementSize = Unsafe.SizeOf<T>();
            if (byteCount % elementSize != 0)
                throw new InvalidOperationException(
                    "corrupt wire: blittable array byte count is not a multiple of element size");
            if (byteCount == 0) return Array.Empty<T>();
            int count = byteCount / elementSize;
            T[] result = new T[count];
            // Fast path via Marshal.Copy's per-type overloads for the types
            // that ship with one (byte, short, int, long, float, double).
            // Unsigned primitives and any other unmanaged T fall through to
            // a byte scratch buffer reinterpreted via MemoryMarshal.AsBytes.
            // Little-endian hosts only; big-endian would need byte-swap.
            switch (result)
            {
                case byte[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, byteCount);
                    break;
                case short[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case int[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case long[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case float[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case double[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                default:
                    byte[] scratch = new byte[byteCount];
                    Marshal.Copy(_ptr + _pos, scratch, 0, byteCount);
                    scratch.AsSpan().CopyTo(MemoryMarshal.AsBytes(result.AsSpan()));
                    break;
            }
            _pos += byteCount;
            return result;
        }

        internal bool[] ReadBoolArray()
        {
            int count = _length - _pos;
            if (count < 0)
                throw new InvalidOperationException("corrupt wire: read position past end");
            if (count == 0) return Array.Empty<bool>();
            bool[] result = new bool[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = Marshal.ReadByte(_ptr, _pos + i) != 0;
            }
            _pos += count;
            return result;
        }

        internal nint[] ReadNIntArray()
        {
            long[] raw = ReadBlittableArray<long>();
            nint[] result = new nint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nint)raw[i];
            return result;
        }

        internal nuint[] ReadNUIntArray()
        {
            long[] raw = ReadBlittableArray<long>();
            nuint[] result = new nuint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nuint)(ulong)raw[i];
            return result;
        }

        /// Reads a length-prefixed `T[]` whose bytes follow the 4-byte count.
        /// Used for `Vec<T>` nested inside an encoded outer container, where
        /// the outer codec needs an explicit count to know how far to advance
        /// the cursor between sibling elements.
        internal T[] ReadLengthPrefixedBlittableArray<T>() where T : unmanaged
        {
            int count = ReadI32();
            if (count < 0) throw new InvalidOperationException("corrupt wire: negative array length");
            if (count == 0) return Array.Empty<T>();
            int elementSize = Unsafe.SizeOf<T>();
            int byteCount = checked(count * elementSize);
            Require(byteCount, "blittable array payload");
            T[] result = new T[count];
            switch (result)
            {
                case byte[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, byteCount);
                    break;
                case short[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case int[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case long[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case float[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                case double[] dst:
                    Marshal.Copy(_ptr + _pos, dst, 0, count);
                    break;
                default:
                    byte[] scratch = new byte[byteCount];
                    Marshal.Copy(_ptr + _pos, scratch, 0, byteCount);
                    scratch.AsSpan().CopyTo(MemoryMarshal.AsBytes(result.AsSpan()));
                    break;
            }
            _pos += byteCount;
            return result;
        }

        internal bool[] ReadLengthPrefixedBoolArray()
        {
            int count = ReadI32();
            if (count < 0) throw new InvalidOperationException("corrupt wire: negative array length");
            if (count == 0) return Array.Empty<bool>();
            Require(count, "bool array payload");
            bool[] result = new bool[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = Marshal.ReadByte(_ptr, _pos + i) != 0;
            }
            _pos += count;
            return result;
        }

        internal nint[] ReadLengthPrefixedNIntArray()
        {
            long[] raw = ReadLengthPrefixedBlittableArray<long>();
            nint[] result = new nint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nint)raw[i];
            return result;
        }

        internal nuint[] ReadLengthPrefixedNUIntArray()
        {
            long[] raw = ReadLengthPrefixedBlittableArray<long>();
            nuint[] result = new nuint[raw.Length];
            for (int i = 0; i < raw.Length; i++) result[i] = (nuint)(ulong)raw[i];
            return result;
        }

        /// Reads a length-prefixed array whose elements are decoded by
        /// invoking `read` once per slot. Used for `Vec<T>` where each
        /// element has variable wire width (strings, nested encoded vecs,
        /// records).
        internal T[] ReadEncodedArray<T>(Func<WireReader, T> read)
        {
            int count = ReadI32();
            if (count < 0) throw new InvalidOperationException("corrupt wire: negative array length");
            if (count == 0) return Array.Empty<T>();
            T[] result = new T[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = read(this);
            }
            return result;
        }

        private void Require(int n, string kind)
        {
            if (n < 0 || n > _length - _pos) throw new InvalidOperationException("corrupt wire: truncated " + kind);
        }
    }

{% endif %}
{%- if module.needs_wire_writer() %}
    internal sealed class WireWriter : IDisposable
    {
        private const int MinCapacity = 16;

        private byte[] _buffer;
        private int _pos;
        private bool _disposed;

        internal WireWriter(int initialCapacity)
        {
            int cap = Math.Max(initialCapacity, MinCapacity);
            _buffer = ArrayPool<byte>.Shared.Rent(cap);
            _pos = 0;
            _disposed = false;
        }

        /// <summary>Copy the written bytes into a fresh managed array.</summary>
        internal byte[] ToArray()
        {
            if (_pos == 0) return Array.Empty<byte>();
            byte[] result = new byte[_pos];
            Buffer.BlockCopy(_buffer, 0, result, 0, _pos);
            return result;
        }

        internal int Position => _pos;

        internal void WriteBool(bool v) { EnsureCapacity(1); _buffer[_pos++] = (byte)(v ? 1 : 0); }
        internal void WriteI8(sbyte v) { EnsureCapacity(1); _buffer[_pos++] = (byte)v; }
        internal void WriteU8(byte v) { EnsureCapacity(1); _buffer[_pos++] = v; }
        internal void WriteI16(short v) { EnsureCapacity(2); BinaryPrimitives.WriteInt16LittleEndian(_buffer.AsSpan(_pos), v); _pos += 2; }
        internal void WriteU16(ushort v) { EnsureCapacity(2); BinaryPrimitives.WriteUInt16LittleEndian(_buffer.AsSpan(_pos), v); _pos += 2; }
        internal void WriteI32(int v) { EnsureCapacity(4); BinaryPrimitives.WriteInt32LittleEndian(_buffer.AsSpan(_pos), v); _pos += 4; }
        internal void WriteU32(uint v) { EnsureCapacity(4); BinaryPrimitives.WriteUInt32LittleEndian(_buffer.AsSpan(_pos), v); _pos += 4; }
        internal void WriteI64(long v) { EnsureCapacity(8); BinaryPrimitives.WriteInt64LittleEndian(_buffer.AsSpan(_pos), v); _pos += 8; }
        internal void WriteU64(ulong v) { EnsureCapacity(8); BinaryPrimitives.WriteUInt64LittleEndian(_buffer.AsSpan(_pos), v); _pos += 8; }
        internal void WriteF32(float v) { EnsureCapacity(4); BinaryPrimitives.WriteSingleLittleEndian(_buffer.AsSpan(_pos), v); _pos += 4; }
        internal void WriteF64(double v) { EnsureCapacity(8); BinaryPrimitives.WriteDoubleLittleEndian(_buffer.AsSpan(_pos), v); _pos += 8; }

        internal void WriteNInt(nint v) => WriteI64((long)v);
        internal void WriteNUInt(nuint v) => WriteU64((ulong)v);

        // Built-in value-type writers. Floor-divide so a pre-epoch
        // DateTime (negative tick offset from UnixEpoch) lands at the
        // floor-second with a positive nanos remainder, matching the
        // Rust SystemTime wire shape and Java's WriteInstant. All
        // references go through `global::System.` so a user contract
        // that defines a same-named record can't shadow the signature.
        // Each helper is gated on the contract actually using that
        // builtin so unused helpers don't appear in the generated source.
{%- if module.uses_duration_builtin() %}
        internal void WriteDuration(global::System.TimeSpan v)
        {
            if (v.Ticks < 0L) throw new ArgumentException("duration must be non-negative", nameof(v));
            long seconds = v.Ticks / global::System.TimeSpan.TicksPerSecond;
            long subTicks = v.Ticks % global::System.TimeSpan.TicksPerSecond;
            WriteI64(seconds);
            WriteI32(checked((int)(subTicks * 100)));
        }
{%- endif %}
{%- if module.uses_system_time_builtin() %}

        internal void WriteDateTime(global::System.DateTime v)
        {
            if (v.Kind == global::System.DateTimeKind.Unspecified)
                throw new ArgumentException("DateTime must have Kind=Utc or Kind=Local; Unspecified is ambiguous on the wire.", nameof(v));
            long ticks = (v.ToUniversalTime() - global::System.DateTime.UnixEpoch).Ticks;
            long seconds = ticks / global::System.TimeSpan.TicksPerSecond;
            long subTicks = ticks % global::System.TimeSpan.TicksPerSecond;
            if (subTicks < 0)
            {
                seconds -= 1;
                subTicks += global::System.TimeSpan.TicksPerSecond;
            }
            WriteI64(seconds);
            WriteI32(checked((int)(subTicks * 100)));
        }
{%- endif %}
{%- if module.uses_uuid_builtin() %}

        internal void WriteUuid(global::System.Guid v)
        {
            Span<byte> bytes = stackalloc byte[16];
            if (!v.TryWriteBytes(bytes, bigEndian: true, out _))
                throw new InvalidOperationException("Guid.TryWriteBytes failed");
            WriteI64(BinaryPrimitives.ReadInt64BigEndian(bytes[0..8]));
            WriteI64(BinaryPrimitives.ReadInt64BigEndian(bytes[8..16]));
        }
{%- endif %}
{%- if module.uses_url_builtin() %}

        internal void WriteUri(global::System.Uri v) => WriteString(v.ToString());
{%- endif %}

        internal void WriteString(string v)
        {
            int byteCount = Encoding.UTF8.GetByteCount(v);
            WriteI32(byteCount);
            if (byteCount == 0) return;
            EnsureCapacity(byteCount);
            Encoding.UTF8.GetBytes(v, 0, v.Length, _buffer, _pos);
            _pos += byteCount;
        }

        internal void WriteBytes(byte[] v)
        {
            WriteI32(v.Length);
            if (v.Length == 0) return;
            EnsureCapacity(v.Length);
            Buffer.BlockCopy(v, 0, _buffer, _pos, v.Length);
            _pos += v.Length;
        }

        internal void WriteBlittableArray<T>(T[] v) where T : unmanaged
        {
            WriteI32(v.Length);
            if (v.Length == 0) return;
            int byteCount = checked(v.Length * Unsafe.SizeOf<T>());
            EnsureCapacity(byteCount);
            // Reinterpret the source T[] as bytes and block-copy into the
            // managed buffer. Zero extra allocations, one copy. Little-endian
            // hosts only; big-endian would need byte-swap.
            MemoryMarshal.AsBytes(v.AsSpan()).CopyTo(_buffer.AsSpan(_pos, byteCount));
            _pos += byteCount;
        }

        internal void WriteBoolArray(bool[] v)
        {
            WriteI32(v.Length);
            if (v.Length == 0) return;
            EnsureCapacity(v.Length);
            for (int i = 0; i < v.Length; i++)
            {
                _buffer[_pos + i] = (byte)(v[i] ? 1 : 0);
            }
            _pos += v.Length;
        }

        internal void WriteNIntArray(nint[] v)
        {
            long[] widened = new long[v.Length];
            for (int i = 0; i < v.Length; i++) widened[i] = (long)v[i];
            WriteBlittableArray(widened);
        }

        internal void WriteNUIntArray(nuint[] v)
        {
            long[] widened = new long[v.Length];
            for (int i = 0; i < v.Length; i++) widened[i] = (long)(ulong)v[i];
            WriteBlittableArray(widened);
        }

        /// Byte size of an encoded `T[]`: the `sizeof(int)` length prefix
        /// plus the per-element size. Size expressions for `Vec<T>` with
        /// variable-width elements (strings, nested vecs, records) use this
        /// helper to drive the `WireWriter` pre-size so its initial
        /// capacity matches the payload and no growth copy is needed.
        internal static int EncodedArraySize<T>(T[] v, Func<T, int> sizer)
        {
            int total = sizeof(int);
            for (int i = 0; i < v.Length; i++) total += sizer(v[i]);
            return total;
        }

        private void EnsureCapacity(int additional)
        {
            if (_pos + additional <= _buffer.Length) return;
            int next = Math.Max(_buffer.Length * 2, _pos + additional);
            byte[] grown = ArrayPool<byte>.Shared.Rent(next);
            Buffer.BlockCopy(_buffer, 0, grown, 0, _pos);
            ArrayPool<byte>.Shared.Return(_buffer);
            _buffer = grown;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            ArrayPool<byte>.Shared.Return(_buffer);
            _buffer = Array.Empty<byte>();
        }
    }

{% endif %}
{%- if module.needs_bolt_exception() %}
    /// <summary>
    /// Thrown by generated wrapper methods and fallible constructors
    /// when a Rust <c>Result</c> returns an <c>Err</c> whose payload
    /// isn't a typed <c>#[error]</c> enum or record. The message is the
    /// underlying error rendered as a string. Catch this to handle
    /// plain <c>Result&lt;_, String&gt;</c> errors and untyped <c>Err</c>
    /// payloads; catch a typed <c>&lt;Name&gt;Exception</c> instead
    /// when the Rust side declared an <c>#[error]</c> type.
    /// </summary>
    public sealed class BoltException : Exception
    {
        public BoltException(string message) : base(message) { }
    }

{% endif %}
{%- if module.needs_callback_runtime() %}
    [StructLayout(LayoutKind.Sequential)]
    internal struct BoltFFICallbackHandle
    {
        public ulong handle;
        public IntPtr vtable;

        internal static BoltFFICallbackHandle Null => default;
        internal bool IsNull => handle == 0 || vtable == IntPtr.Zero;
    }

{% endif %}
{%- if module.needs_result_runtime() %}
    public readonly struct BoltFFIUnit
    {
    }

    public readonly struct BoltFFIResult<TOk, TErr>
    {
        private readonly TOk _okValue;
        private readonly TErr _errValue;

        private BoltFFIResult(TOk okValue, TErr errValue, bool isOk)
        {
            _okValue = okValue;
            _errValue = errValue;
            IsOk = isOk;
        }

        public bool IsOk { get; }
        public TOk OkValue => _okValue;
        public TErr ErrValue => _errValue;

        public static BoltFFIResult<TOk, TErr> Ok(TOk value) => new(value, default!, true);
        public static BoltFFIResult<TOk, TErr> Err(TErr value) => new(default!, value, false);
    }

{% endif %}
{%- if module.needs_callback_runtime() %}
    internal static class BoltFFICallbackReturn
    {
        internal static unsafe void Store(byte[] bytes, out IntPtr outPtr, out UIntPtr outLen)
        {
            if (bytes.Length == 0)
            {
                outPtr = IntPtr.Zero;
                outLen = UIntPtr.Zero;
                return;
            }

            void* allocated = NativeMemory.Alloc((nuint)bytes.Length);
            Marshal.Copy(bytes, 0, (IntPtr)allocated, bytes.Length);
            outPtr = (IntPtr)allocated;
            outLen = (UIntPtr)bytes.Length;
        }

        internal static unsafe void Free(IntPtr ptr)
        {
            if (ptr != IntPtr.Zero)
            {
                NativeMemory.Free((void*)ptr);
            }
        }
    }

{% endif %}
{%- if module.has_async() %}
    internal static class BoltFFIAsync
    {
        private const sbyte PollReady = 0;
        private const int StatusOk = 0;
        private const int StatusCancelled = 4;

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        internal delegate void RustFutureContinuationCallback(ulong callbackData, sbyte pollResult);

        private static readonly RustFutureContinuationCallback Continuation = OnFutureContinuation;
        private static readonly global::System.Collections.Concurrent.ConcurrentDictionary<ulong, global::System.Threading.Tasks.TaskCompletionSource<sbyte>> Continuations = new();
        private static long _nextContinuationId;

        internal static async global::System.Threading.Tasks.Task<T> CallAsync<T>(
            global::System.Func<IntPtr> start,
            global::System.Action<IntPtr, ulong, RustFutureContinuationCallback> poll,
            global::System.Func<IntPtr, T> complete,
            global::System.Action<IntPtr> cancel,
            global::System.Action<IntPtr> free,
            global::System.Threading.CancellationToken cancellationToken)
        {
            var future = new RustFutureOwner(start(), cancel, free);
            global::System.Threading.CancellationTokenRegistration cancellationRegistration = cancellationToken.Register(static state =>
            {
                ((RustFutureOwner)state!).Cancel();
            }, future);

            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                while (true)
                {
                    sbyte pollResult = await PollOnceAsync(future, poll, cancellationToken).ConfigureAwait(false);
                    cancellationToken.ThrowIfCancellationRequested();
                    if (pollResult == PollReady)
                    {
                        break;
                    }
                    await RepollOnThreadPoolAsync().ConfigureAwait(false);
                    cancellationToken.ThrowIfCancellationRequested();
                }

                cancellationRegistration.Dispose();
                return complete(future.Handle);
            }
            catch (global::System.OperationCanceledException)
            {
                future.Cancel();
                throw;
            }
            finally
            {
                cancellationRegistration.Dispose();
                future.Release();
            }
        }

        internal static global::System.Threading.Tasks.Task CallAsyncVoid(
            global::System.Func<IntPtr> start,
            global::System.Action<IntPtr, ulong, RustFutureContinuationCallback> poll,
            global::System.Action<IntPtr> complete,
            global::System.Action<IntPtr> cancel,
            global::System.Action<IntPtr> free,
            global::System.Threading.CancellationToken cancellationToken)
        {
            return CallAsync<VoidResult>(
                start,
                poll,
                future =>
                {
                    complete(future);
                    return default;
                },
                cancel,
                free,
                cancellationToken);
        }

        internal static void ThrowIfStatus(FfiStatus status, global::System.Threading.CancellationToken cancellationToken)
        {
            switch (status.code)
            {
                case StatusOk:
                    return;
                case StatusCancelled:
                    throw new global::System.OperationCanceledException(cancellationToken);
                default:
                    throw new global::System.InvalidOperationException($"FFI async completion failed with status code {status.code}");
            }
        }

        private static global::System.Threading.Tasks.Task<sbyte> PollOnceAsync(
            RustFutureOwner future,
            global::System.Action<IntPtr, ulong, RustFutureContinuationCallback> poll,
            global::System.Threading.CancellationToken cancellationToken)
        {
            var completion = new global::System.Threading.Tasks.TaskCompletionSource<sbyte>(global::System.Threading.Tasks.TaskCreationOptions.RunContinuationsAsynchronously);
            ulong callbackData = RegisterContinuation(completion);
            try
            {
                poll(future.Handle, callbackData, Continuation);
            }
            catch
            {
                Continuations.TryRemove(callbackData, out _);
                throw;
            }

            var pollState = new PollState(callbackData, cancellationToken);
            global::System.Threading.CancellationTokenRegistration cancellationRegistration = cancellationToken.Register(static state =>
            {
                var current = (PollState)state!;
                if (Continuations.TryRemove(current.CallbackData, out var pending))
                {
                    pending.TrySetCanceled(current.CancellationToken);
                }
            }, pollState);

            return AwaitPollAsync(completion.Task, cancellationRegistration);
        }

        private static async global::System.Threading.Tasks.Task<sbyte> AwaitPollAsync(
            global::System.Threading.Tasks.Task<sbyte> pollTask,
            global::System.Threading.CancellationTokenRegistration cancellationRegistration)
        {
            try
            {
                return await pollTask.ConfigureAwait(false);
            }
            finally
            {
                cancellationRegistration.Dispose();
            }
        }

        private static global::System.Threading.Tasks.Task RepollOnThreadPoolAsync()
        {
            var completion = new global::System.Threading.Tasks.TaskCompletionSource<object?>(global::System.Threading.Tasks.TaskCreationOptions.RunContinuationsAsynchronously);
            global::System.Threading.ThreadPool.QueueUserWorkItem(static state =>
            {
                ((global::System.Threading.Tasks.TaskCompletionSource<object?>)state!).TrySetResult(null);
            }, completion);
            return completion.Task;
        }

        private static ulong RegisterContinuation(global::System.Threading.Tasks.TaskCompletionSource<sbyte> completion)
        {
            while (true)
            {
                ulong callbackData = unchecked((ulong)global::System.Threading.Interlocked.Increment(ref _nextContinuationId));
                if (callbackData != 0 && Continuations.TryAdd(callbackData, completion))
                {
                    return callbackData;
                }
            }
        }

        private static void OnFutureContinuation(ulong callbackData, sbyte pollResult)
        {
            if (Continuations.TryRemove(callbackData, out var completion))
            {
                completion.TrySetResult(pollResult);
            }
        }

        private sealed class PollState
        {
            internal PollState(ulong callbackData, global::System.Threading.CancellationToken cancellationToken)
            {
                CallbackData = callbackData;
                CancellationToken = cancellationToken;
            }

            internal ulong CallbackData { get; }
            internal global::System.Threading.CancellationToken CancellationToken { get; }
        }

        private sealed class RustFutureOwner
        {
            private readonly global::System.Action<IntPtr> _cancel;
            private readonly global::System.Action<IntPtr> _free;
            private readonly object _gate = new();
            private IntPtr _handle;
            private bool _cancelled;
            private bool _released;

            internal RustFutureOwner(IntPtr handle, global::System.Action<IntPtr> cancel, global::System.Action<IntPtr> free)
            {
                _handle = handle;
                _cancel = cancel;
                _free = free;
            }

            internal IntPtr Handle
            {
                get
                {
                    lock (_gate)
                    {
                        if (_released || _handle == IntPtr.Zero)
                        {
                            throw new global::System.ObjectDisposedException(nameof(RustFutureOwner));
                        }
                        return _handle;
                    }
                }
            }

            internal void Cancel()
            {
                lock (_gate)
                {
                    if (_cancelled || _released || _handle == IntPtr.Zero)
                    {
                        return;
                    }
                    _cancelled = true;
                    _cancel(_handle);
                }
            }

            internal void Release()
            {
                lock (_gate)
                {
                    if (_released || _handle == IntPtr.Zero)
                    {
                        return;
                    }
                    _released = true;
                    IntPtr handle = _handle;
                    _handle = IntPtr.Zero;
                    _free(handle);
                }
            }
        }

        private readonly struct VoidResult
        {
        }
    }

{% endif %}
{%- if module.has_streams() %}
    internal static class BoltFFIStream
    {
        internal const int DefaultBatchSize = 16;
        internal const int WaitTimeoutMilliseconds = 50;
        internal const int WaitResultUnsubscribed = -1;
    }

{% endif %}
    internal static class NativeMethods
    {
        internal const string LibName = "{{ module.lib_name }}";
{%- if module.needs_last_error() %}

        [DllImport(LibName, EntryPoint = "boltffi_last_error_message")]
        private static extern FfiStatus LastErrorMessage(out FfiString message);

        [DllImport(LibName, EntryPoint = "boltffi_free_string")]
        private static extern void FreeString(FfiString message);

        internal static string TakeLastErrorMessage(string fallback)
        {
            FfiStatus status = LastErrorMessage(out FfiString message);
            if (status.code != 0) return fallback;

            try
            {
                if (message.ptr == IntPtr.Zero || message.len == UIntPtr.Zero) return fallback;
                return Marshal.PtrToStringUTF8(message.ptr, checked((int)(nuint)message.len)) ?? fallback;
            }
            finally
            {
                FreeString(message);
            }
        }
{%- endif %}
{%- if module.needs_ffi_buf() %}

        [DllImport(LibName, EntryPoint = "{{ module.free_buf_ffi_name }}")]
        internal static extern void FreeBuf(FfiBuf buf);
{%- endif %}
{%- for func in module.functions %}

{%- if func.is_async() %}
{%- match func.async_call %}
{%- when Some with (ac) %}
        [DllImport(LibName, EntryPoint = "{{ func.ffi_name }}")]
        internal static extern IntPtr {{ func.name }}({{ func.native_param_list() }});

        [DllImport(LibName, EntryPoint = "{{ ac.poll_ffi_name }}")]
        internal static extern void {{ ac.poll_method_name }}(IntPtr future, ulong callbackData, BoltFFIAsync.RustFutureContinuationCallback callback);

        [DllImport(LibName, EntryPoint = "{{ ac.complete_ffi_name }}")]
{%- if func.return_type.is_bool() && !func.return_kind.native_returns_ffi_buf() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ func.native_return_type() }} {{ ac.complete_method_name }}(IntPtr future, out FfiStatus status);

        [DllImport(LibName, EntryPoint = "{{ ac.cancel_ffi_name }}")]
        internal static extern void {{ ac.cancel_method_name }}(IntPtr future);

        [DllImport(LibName, EntryPoint = "{{ ac.free_ffi_name }}")]
        internal static extern void {{ ac.free_method_name }}(IntPtr future);
{%- when None %}
{%- endmatch %}
{%- else %}
        [DllImport(LibName, EntryPoint = "{{ func.ffi_name }}")]
{%- if func.return_type.is_bool() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ func.native_return_type() }} {{ func.name }}({{ func.native_param_list() }});
{%- endif %}
{%- endfor %}
{%- for enumeration in module.enums %}
{%- for method in enumeration.methods %}

{%- if method.is_async() %}
{%- match method.async_call %}
{%- when Some with (ac) %}
        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
        internal static extern IntPtr {{ method.native_method_name }}({{ method.native_param_list(enumeration.class_name, false) }});

        [DllImport(LibName, EntryPoint = "{{ ac.poll_ffi_name }}")]
        internal static extern void {{ ac.poll_method_name }}(IntPtr future, ulong callbackData, BoltFFIAsync.RustFutureContinuationCallback callback);

        [DllImport(LibName, EntryPoint = "{{ ac.complete_ffi_name }}")]
{%- if method.return_type.is_bool() && !method.return_kind.native_returns_ffi_buf() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ ac.complete_method_name }}(IntPtr future, out FfiStatus status);

        [DllImport(LibName, EntryPoint = "{{ ac.cancel_ffi_name }}")]
        internal static extern void {{ ac.cancel_method_name }}(IntPtr future);

        [DllImport(LibName, EntryPoint = "{{ ac.free_ffi_name }}")]
        internal static extern void {{ ac.free_method_name }}(IntPtr future);
{%- when None %}
{%- endmatch %}
{%- else %}
        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
{%- if method.return_type.is_bool() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ method.native_method_name }}({{ method.native_param_list(enumeration.class_name, false) }});
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- for record in module.records %}
{%- for method in record.methods %}

{%- if method.is_async() %}
{%- match method.async_call %}
{%- when Some with (ac) %}
        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
        internal static extern IntPtr {{ method.native_method_name }}({{ method.native_param_list(record.class_name, *record.is_blittable) }});

        [DllImport(LibName, EntryPoint = "{{ ac.poll_ffi_name }}")]
        internal static extern void {{ ac.poll_method_name }}(IntPtr future, ulong callbackData, BoltFFIAsync.RustFutureContinuationCallback callback);

        [DllImport(LibName, EntryPoint = "{{ ac.complete_ffi_name }}")]
{%- if method.return_type.is_bool() && !method.return_kind.native_returns_ffi_buf() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ ac.complete_method_name }}(IntPtr future, out FfiStatus status);

        [DllImport(LibName, EntryPoint = "{{ ac.cancel_ffi_name }}")]
        internal static extern void {{ ac.cancel_method_name }}(IntPtr future);

        [DllImport(LibName, EntryPoint = "{{ ac.free_ffi_name }}")]
        internal static extern void {{ ac.free_method_name }}(IntPtr future);
{%- when None %}
{%- endmatch %}
{%- else %}
        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
{%- if method.return_type.is_bool() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ method.native_method_name }}({{ method.native_param_list(record.class_name, *record.is_blittable) }});
{%- endif %}
{%- endfor %}
{%- endfor %}
{%- for class in module.classes %}

        [DllImport(LibName, EntryPoint = "{{ class.ffi_free }}")]
        internal static extern void {{ class.native_free_method_name }}(IntPtr handle);
{%- for ctor in class.constructors %}

        [DllImport(LibName, EntryPoint = "{{ ctor.ffi_name }}")]
        internal static extern IntPtr {{ ctor.native_method_name }}({{ ctor.native_param_list() }});
{%- endfor %}
{%- for method in class.methods %}

{%- if method.is_async() %}
{%- match method.async_call %}
{%- when Some with (ac) %}
        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
        internal static extern IntPtr {{ method.native_method_name }}({{ method.native_param_list(class.class_name, false) }});

        [DllImport(LibName, EntryPoint = "{{ ac.poll_ffi_name }}")]
        internal static extern void {{ ac.poll_method_name }}(IntPtr future, ulong callbackData, BoltFFIAsync.RustFutureContinuationCallback callback);

        [DllImport(LibName, EntryPoint = "{{ ac.complete_ffi_name }}")]
{%- if method.return_type.is_bool() && !method.return_kind.native_returns_ffi_buf() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ ac.complete_method_name }}(IntPtr future, out FfiStatus status);

        [DllImport(LibName, EntryPoint = "{{ ac.cancel_ffi_name }}")]
        internal static extern void {{ ac.cancel_method_name }}(IntPtr future);

        [DllImport(LibName, EntryPoint = "{{ ac.free_ffi_name }}")]
        internal static extern void {{ ac.free_method_name }}(IntPtr future);
{%- when None %}
{%- endmatch %}
{%- else %}
        [DllImport(LibName, EntryPoint = "{{ method.ffi_name }}")]
{%- if method.return_type.is_bool() %}
        [return: MarshalAs(UnmanagedType.I1)]
{%- endif %}
        internal static extern {{ method.native_return_type() }} {{ method.native_method_name }}({{ method.native_param_list(class.class_name, false) }});
{%- endif %}
{%- endfor %}
{%- for stream in class.streams %}

        [DllImport(LibName, EntryPoint = "{{ stream.subscribe_ffi_name }}")]
        internal static extern IntPtr {{ stream.subscribe_method_name }}(IntPtr handle);

        [DllImport(LibName, EntryPoint = "{{ stream.pop_batch_ffi_name }}")]
{%- match stream.delivery %}
{%- when CSharpStreamDelivery::Direct %}
        internal static extern UIntPtr {{ stream.pop_batch_method_name }}(IntPtr subscription, IntPtr outputPtr, UIntPtr outputCapacity);
{%- when CSharpStreamDelivery::WireEncoded { .. } %}
        internal static extern FfiBuf {{ stream.pop_batch_method_name }}(IntPtr subscription, UIntPtr maxCount);
{%- endmatch %}

        [DllImport(LibName, EntryPoint = "{{ stream.wait_ffi_name }}")]
        internal static extern int {{ stream.wait_method_name }}(IntPtr subscription, int timeoutMilliseconds);

        [DllImport(LibName, EntryPoint = "{{ stream.unsubscribe_ffi_name }}")]
        internal static extern void {{ stream.unsubscribe_method_name }}(IntPtr subscription);

        [DllImport(LibName, EntryPoint = "{{ stream.free_ffi_name }}")]
        internal static extern void {{ stream.free_method_name }}(IntPtr subscription);
{%- endfor %}
{%- endfor %}
    }
}
