{%- if module.needs_ffi_buf() %}
[StructLayout(LayoutKind.Sequential)]
internal struct FfiBuf
{
public IntPtr ptr;
public UIntPtr len;
public UIntPtr cap;
public UIntPtr align;
}
{% 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 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();
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;
}
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);
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;
}
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 %}
internal static class NativeMethods
{
private const string LibName = "{{ module.lib_name }}";
{%- if module.needs_ffi_buf() %}
[DllImport(LibName, EntryPoint = "{{ module.prefix }}_free_buf")]
internal static extern void FreeBuf(FfiBuf buf);
{%- endif %}
{%- for func in module.functions %}
[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() }});
{%- endfor %}
}
}