Skip to content

File buffer.h

File List > buffer > buffer.h

Go to the documentation of this file

#ifndef DP_BUFFER_H
#define DP_BUFFER_H

/* -------------------------------------------------------------------------
 * Platform includes
 * ---------------------------------------------------------------------- */
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0A00 /* Windows 10 – required for VirtualAlloc2 */
#elif _WIN32_WINNT < 0x0A00
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0A00
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#else /* POSIX ------------------------------------------------------------   \
       */
#ifdef __linux__
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* memfd_create */
#endif
#endif
#ifdef __APPLE__
#define _DARWIN_C_SOURCE /* MAP_ANON */
#endif
#include <fcntl.h> /* shm_open on macOS */
#include <stdio.h> /* snprintf */
#include <sys/mman.h>
#include <unistd.h>
#endif /* _WIN32 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* macOS uses MAP_ANON, Linux uses MAP_ANONYMOUS. Normalize to MAP_ANONYMOUS.
 */
#if !defined(_WIN32) && !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
#include <stdlib.h>
#include <string.h>

/* -------------------------------------------------------------------------
 * Architecture: spin-hint
 * ---------------------------------------------------------------------- */
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__)
#include <immintrin.h>
#define DP_SPIN_HINT() _mm_pause ()
#else
#define DP_SPIN_HINT() ((void)0)
#endif

/* -------------------------------------------------------------------------
 * Compat: alignment
 * ---------------------------------------------------------------------- */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#include <stdalign.h>
#define DP_ALIGN(n) alignas (n)
#else
#define DP_ALIGN(n) __attribute__ ((aligned (n)))
#endif

/* -------------------------------------------------------------------------
 * Compat: compile-time power-of-two assert
 * ---------------------------------------------------------------------- */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define DP_ASSERT_PWR2(n)                                                     \
  _Static_assert (((n) & ((n) - 1)) == 0, "Size must be power of 2")
#else
#define DP_ASSERT_PWR2(n)                                                     \
  typedef char dp_assert_pwr2_##n[((n) & ((n) - 1)) == 0 ? 1 : -1]
#endif

/* -------------------------------------------------------------------------
 * Atomics (GCC / Clang __atomic built-ins)
 * ---------------------------------------------------------------------- */
#if defined(__GNUC__) || defined(__clang__)
#define DP_LOAD_ACQ(ptr)                                                      \
  __extension__ ({                                                            \
    size_t _v;                                                                \
    __atomic_load ((const size_t *)(ptr), &_v, __ATOMIC_ACQUIRE);             \
    _v;                                                                       \
  })
#define DP_LOAD_RLX(ptr)                                                      \
  __extension__ ({                                                            \
    size_t _v;                                                                \
    __atomic_load ((const size_t *)(ptr), &_v, __ATOMIC_RELAXED);             \
    _v;                                                                       \
  })
#define DP_STORE_REL(ptr, val)                                                \
  __extension__ ({                                                            \
    size_t _v = (val);                                                        \
    __atomic_store ((size_t *)(ptr), &_v, __ATOMIC_RELEASE);                  \
  })
#else
#error "doppler requires GCC or Clang for atomic built-ins."
#endif

#define DP_CACHELINE 64

/* =========================================================================
 * Platform-agnostic memory helpers
 * All platform-specific logic lives HERE, outside any macro, so that
 * #ifdef / #else can be used freely.
 * ====================================================================== */

static inline size_t
dp__page_size (void)
{
#ifdef _WIN32
  SYSTEM_INFO si;
  GetSystemInfo (&si);
  return (size_t)si.dwAllocationGranularity;
#else
  return (size_t)sysconf (_SC_PAGESIZE);
#endif
}

static inline void *
dp__buf_alloc (size_t bytes, void **handle_out)
{
  *handle_out = NULL;

#ifdef _WIN32
  /*
   * Windows ring-buffer using only kernel32.dll functions (Win7+).
   *
   * Strategy: CreateFileMapping + MapViewOfFileEx at adjacent addresses.
   * We use MapViewOfFile to discover a free region, unmap it, then
   * immediately remap twice. A tight retry loop handles the rare case
   * where another thread grabs the address between the unmap and remap.
   */
  DWORD size_hi = (DWORD)((DWORD64)bytes >> 32);
  DWORD size_lo = (DWORD)(bytes & 0xFFFFFFFFULL);

  HANDLE h = CreateFileMappingA (INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
                                 size_hi, size_lo, NULL);
  if (!h)
    return NULL;

  void *addr = NULL;
  for (int attempt = 0; attempt < 1024; ++attempt)
    {
      /* 1. Map once to find a suitably aligned free region. */
      void *hint = MapViewOfFile (h, FILE_MAP_ALL_ACCESS, 0, 0, bytes);
      if (!hint)
        break;
      UnmapViewOfFile (hint);

      /* 2. Remap first view at the hint address. */
      void *v1 = MapViewOfFileEx (h, FILE_MAP_ALL_ACCESS, 0, 0, bytes, hint);
      if (!v1)
        continue; /* someone stole the address – retry */

      /* 3. Remap second view immediately after the first. */
      void *v2 = MapViewOfFileEx (h, FILE_MAP_ALL_ACCESS, 0, 0, bytes,
                                  (char *)v1 + bytes);
      if (v2)
        {
          addr = v1;
          break;
        } /* success */

      UnmapViewOfFile (v1); /* second map failed – retry with new hint */
    }

  if (!addr)
    {
      CloseHandle (h);
      return NULL;
    }
  *handle_out = (void *)h;
  return addr;

#else /* POSIX ----------------------------------------------------------- */

  /* Obtain an anonymous fd for `bytes` of shared memory. */
  int fd;
#ifdef __linux__
  /* Use memfd_create when available; otherwise fall back to the raw
   * syscall to avoid implicit-declaration warnings on older libc. */
#if defined(__GLIBC__)
#include <features.h>
#endif
#include <sys/syscall.h>
#ifndef SYS_memfd_create
#if defined(__x86_64__)
#define SYS_memfd_create 319
#elif defined(__aarch64__)
#define SYS_memfd_create 279
#elif defined(__i386__)
#define SYS_memfd_create 356
#endif
#endif
  fd = (int)syscall (SYS_memfd_create, "dp_shm", 0);
#else
  char name[64];
  /* PID + pointer makes name unique even across concurrent tests. */
  (void)snprintf (name, sizeof (name), "/dp_%d_%p", (int)getpid (),
                  (void *)&bytes);
  fd = shm_open (name, O_RDWR | O_CREAT | O_EXCL, 0600);
  if (fd != -1)
    shm_unlink (name); /* unlink immediately; fd keeps it alive */
#endif
  if (fd == -1)
    return NULL;
  if (ftruncate (fd, (off_t)bytes) == -1)
    {
      close (fd);
      return NULL;
    }

  /* Reserve 2*bytes of virtual address space. */
  void *addr
      = mmap (NULL, 2 * bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (addr == MAP_FAILED)
    {
      close (fd);
      return NULL;
    }

  /* Map lower half. */
  if (mmap (addr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0)
      == MAP_FAILED)
    {
      munmap (addr, 2 * bytes);
      close (fd);
      return NULL;
    }
  /* Map upper half (same physical pages). */
  if (mmap ((char *)addr + bytes, bytes, PROT_READ | PROT_WRITE,
            MAP_SHARED | MAP_FIXED, fd, 0)
      == MAP_FAILED)
    {
      munmap (addr, 2 * bytes);
      close (fd);
      return NULL;
    }
  close (fd); /* fd no longer needed once both views are established */
  return addr;
#endif /* _WIN32 */
}

static inline void
dp__buf_free (void *addr, size_t bytes, void *handle)
{
#ifdef _WIN32
  UnmapViewOfFile (addr);
  UnmapViewOfFile ((char *)addr + bytes);
  if (handle)
    CloseHandle ((HANDLE)handle);
#else
  munmap (addr, 2 * bytes);
  (void)handle;
#endif
}

/* =========================================================================
 * DECLARE_DP_BUFFER(name, type)
 *
 * Generates a complete type-specific circular-buffer implementation.
 * The macro itself contains NO #ifdef, keeping it portable by delegating
 * all platform work to dp__buf_alloc / dp__buf_free above.
 * ====================================================================== */

#define DECLARE_DP_BUFFER(name, type)                                         \
                                                                              \
                                                \
  typedef struct                                                              \
  {                                                                           \
    type *data;                          \
    size_t mask;                    \
    size_t capacity;                \
    void *_handle;       \
    DP_ALIGN (DP_CACHELINE) volatile size_t head;        \
    DP_ALIGN (DP_CACHELINE) volatile size_t tail;        \
    DP_ALIGN (DP_CACHELINE) volatile size_t dropped;      \
  } dp_##name##_t;                                                            \
                                                                              \
                                                                         \
  static inline dp_##name##_t *dp_##name##_create (size_t n_samples)          \
  {                                                                           \
    if (n_samples == 0 || (n_samples & (n_samples - 1)) != 0)                 \
      return NULL;                                                            \
    /* sizeof(type)*2 (bytes per complex sample) and the page size are both   \
       powers of two, and n_samples is a power of two, so the mirror unit is  \
       a power of two. Rounding n_samples up until the unit reaches one page  \
       therefore lands on an exact page multiple — capacity stays a power of  \
       two. */                                                                \
    size_t page = dp__page_size ();                                           \
    size_t elem = sizeof (type) * 2;                                          \
    while (n_samples * elem < page)                                           \
      n_samples <<= 1;                                                        \
    size_t bytes = n_samples * elem;                                          \
    void *handle = NULL;                                                      \
    void *addr = dp__buf_alloc (bytes, &handle);                              \
    if (!addr)                                                                \
      return NULL;                                                            \
    dp_##name##_t *ab = (dp_##name##_t *)calloc (1, sizeof (dp_##name##_t)); \
    if (!ab)                                                                  \
      {                                                                       \
        dp__buf_free (addr, bytes, handle);                                   \
        return NULL;                                                          \
      }                                                                       \
    ab->data = (type *)addr;                                                  \
    ab->capacity = n_samples;                                                 \
    ab->mask = n_samples - 1;                                                 \
    ab->_handle = handle;                                                     \
    return ab;                                                                \
  }                                                                           \
                                                                              \
              \
  static inline void dp_##name##_destroy (dp_##name##_t *ab)                  \
  {                                                                           \
    dp__buf_free (ab->data, ab->capacity * sizeof (type) * 2, ab->_handle);   \
    free (ab);                                                                \
  }                                                                           \
                                                                              \
                                                                         \
  static inline bool dp_##name##_write (dp_##name##_t *ab, const type *src,   \
                                        size_t n)                             \
  {                                                                           \
    size_t h = DP_LOAD_RLX (&ab->head);                                       \
    size_t t = DP_LOAD_ACQ (&ab->tail);                                       \
    if ((ab->capacity - (h - t)) < n)                                         \
      {                                                                       \
        __atomic_fetch_add (&ab->dropped, n, __ATOMIC_RELAXED);               \
        return false;                                                         \
      }                                                                       \
    memcpy (&ab->data[(h & ab->mask) * 2], src, n * sizeof (type) * 2);       \
    DP_STORE_REL (&ab->head, h + n);                                          \
    return true;                                                              \
  }                                                                           \
                                                                              \
                                                                         \
  static inline type *dp_##name##_wait (dp_##name##_t *ab, size_t n)          \
  {                                                                           \
    size_t h, t;                                                              \
    while (((h = DP_LOAD_ACQ (&ab->head)) - (t = DP_LOAD_RLX (&ab->tail)))    \
           < n)                                                               \
      {                                                                       \
        DP_SPIN_HINT ();                                                      \
      }                                                                       \
    (void)h;                                                                  \
    return &ab->data[(t & ab->mask) * 2];                                     \
  }                                                                           \
                                                                              \
           \
  static inline void dp_##name##_consume (dp_##name##_t *ab, size_t n)        \
  {                                                                           \
    size_t t = DP_LOAD_RLX (&ab->tail);                                       \
    DP_STORE_REL (&ab->tail, t + n);                                          \
  }

/* --- Type instantiations --- */

DECLARE_DP_BUFFER (f32, float)  
DECLARE_DP_BUFFER (f64, double) 
DECLARE_DP_BUFFER (i16, int16_t) 
#endif /* DP_BUFFER_H */