/* -*- mode: c; tab-width: 4; fill-column: 78 -*- */
/* vi: set ts=4 tw=78: */

/*
thread_util.h, Copyright (c) 2014 Dave Odell <dmo2118@gmail.com>

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.  No representations are made about the suitability of this
software for any purpose.  It is provided "as is" without express or
implied warranty.
*/

#ifndef THREAD_UTIL_H
#define THREAD_UTIL_H

/* thread_util.h because C11 took threads.h. */

/* And POSIX threads because there aren't too many systems that support C11
   threads that don't already support POSIX threads.
   ...Not that it would be too hard to convert from the one to the other.
   Or to have both.
 */

/* Beware!
   Multithreading is a great way to add insidious and catastrophic bugs to
   a program. Make sure you understand the risks.

   You may wish to become familiar with race conditions, deadlocks, mutexes,
   condition variables, and, in lock-free code, memory ordering, cache
   hierarchies, etc., before working with threads.

   xlockmore can unlock the screen if you get this wrong. Tools like Valgrind
   (in particular, valgrind --tool=helgrind) can help you get this right.
*/

/*
   The basic strategy for applying threads to a CPU-hungry mode:

   1. Find the CPU-hungry part of the mode.

   2. Change that part so the workload can be divided into N equal-sized
      loads, where N is the number of CPU cores in the machine.
      (For example: with two cores, one core could render even scan lines,
      and the other odd scan lines.)

   2a. Keeping in mind that two threads should not write to the same memory
       at the same time. Specifically, they should not be writing to the
       same cache line at the same time -- so align memory allocation and
       memory accesses to the system cache line size as necessary.

   3. On init_mode, create a threadpool object. This creates N worker threads,
      and each thread creates and owns a user-defined struct. After creation,
      the threads are idle.

   4. On draw_mode, call threadpool_run(). Each thread simultaneously wakes
      up, calls a function that does one of the equal-sized loads, then goes
      back to sleep. The main thread then calls threadpool_wait(), which
      returns once all the worker threads have finished.

      Using this to implement SMP won't necessarily increase performance by
      a factor of N (again, N is CPU cores.). X11 can impose a
      not-insignificant amount of overhead even when simply blitting
      full-screen XImages @ 30 FPS.

      On systems with simultaneous multithreading (a.k.a. Hyper-threading),
      performance gains may be slim to non-existent.
 */

#include "aligned_malloc.h"

#if HAVE_CONFIG_H
/* For HAVE_PTHREAD. */
#	include "config.h"
#endif

#include <stddef.h>

#if HAVE_UNISTD_H
/* For _POSIX_THREADS. */
#	include <unistd.h>
#ifdef __VMS
#undef _POSIX_THREADS
#define _POSIX_THREADS 0
#endif
#endif

/* xlockmore: Win32 support. */
#if defined WIN32
#	include "Xapi.h"
#else
#	include <X11/Xlib.h>
#endif

#if HAVE_PTHREAD
int threads_available(Display *dpy);
#else
#	define threads_available(dpy) (-1)
#endif
/* > 0: Threads are available. This is normally _POSIX_VERSION.
    -1: Threads are not available.
*/

unsigned hardware_concurrency(Display *dpy);
/* This is supposed to return the number of available CPU cores. This number
   isn't necessarily constant: a system administrator can hotplug or
   enable/disable CPUs on certain systems, or the system can deactivate a
   malfunctioning core -- but these are rare.

   If threads are unavailable, this function will return 1.

   This function isn't fast; the result should be cached.
*/

unsigned thread_memory_alignment(Display *dpy);

/* Returns the proper alignment for memory allocated by a thread that is
   shared with other threads.

   A typical CPU accesses the system RAM through a cache, and this cache is
   divided up into cache lines - aligned chunks of memory typically 32 or 64
   bytes in size. Cache faults cause cache lines to be populated from
   memory. And, in a multiprocessing environment, two CPU cores can access the
   same cache line. The consequences of this depend on the CPU model:

   - x86 implements the MESI protocol [1] to maintain cache coherency between
     CPU cores, with a serious performance penalty on both Intel [1] and AMD
     [2].  Intel uses the term "false sharing" to describe two CPU cores
     accessing different memory in the same cache line.

   - ARM allows CPU caches to become inconsistent in this case [3]. Memory
     fences are needed to prevent horrible non-deterministic bugs from
     occurring.  Other CPU architectures have similar behavior to one of the
     above, depending on whether they are "strongly-orderered" (like x86), or
     "weakly-ordered" (like ARM).

   Aligning multithreaded memory accesses according to the cache line size
   neatly sidesteps both issues.

   One complication is that CPU caches are divided up into separate levels,
   and occasionally different levels can have different cache line sizes, so
   to be safe this function returns the largest cache line size among all
   levels.

   If multithreading is not in effect, this returns sizeof(void *), because
   posix_memalign(3) will error out if the alignment is set to be smaller than
   that.

   [1] Intel(R) 64 and IA-32 Architectures Optimization Reference Manual
      (Order Number: 248966-026): 2.1.5 Cache Hierarchy
   [2] Software Optimization Guide for AMD Family 10h Processors (Publication
       #40546): 11.3.4 Data Sharing between Caches
   [3] http://wanderingcoder.net/2011/04/01/arm-memory-ordering/
*/

/*
   Note: aligned_malloc uses posix_memalign(3) when available, or malloc(3)
   otherwise. As of SUSv2 (1997), and *probably* earlier, these are guaranteed
   to be thread-safe. C89 does not discuss threads, or thread safety;
   non-POSIX systems, watch out!
   http://pubs.opengroup.org/onlinepubs/7908799/xsh/threads.html
   http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
*/

/* int thread_malloc(void **ptr, Display *dpy, unsigned size); */
#define thread_malloc(ptr, dpy, size) \
  (aligned_malloc((ptr), thread_memory_alignment(dpy), (size)))

/*
   This simply does a malloc aligned to thread_memory_alignment(). See
   above. On failure, an errno is returned, usually ENOMEM.

   It's possible for two malloc()'d blocks to at least partially share the
   same cache line. When a different thread is writing to each block, then bad
   things can happen (see thread_memory_alignment). Better malloc()
   implementations will divide memory into pools belonging to one thread or
   another, causing memory blocks belonging to different threads to typically
   be located on different memory pages (see getpagesize(2)), mitigating the
   problem in question...but there's nothing stopping threads from passing
   memory to each other. And it's not practical for the system to align each
   block to 64 or 128 byte boundaries -- it's not uncommon to need lots and
   lots of 8-32 byte allocations, and the waste could become a bit excessive.

   Some rules of thumb to take away from this:

   1. Use thread_alloc for memory that might be written to by a thread that
   didn't originally allocate the object.

   2. Use thread_alloc for memory that will be handed from one thread to
   another.

   3. Use malloc if a single thread allocates, reads from, writes to, and
   frees the block of memory.

   Oddly, I (Dave) have not seen this problem described anywhere else.
*/

#define thread_free(ptr) aligned_free(ptr)

#if HAVE_PTHREAD
#	if defined _POSIX_THREADS && _POSIX_THREADS >= 0
/*
   See The Open Group Base Specifications Issue 7, <unistd.h>, Constants for
   Options and Option Groups
   http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/unistd.h.html#tag_13_77_03_02
*/

#		include <pthread.h>

/* Most PThread synchronization functions only fail when they are misused. */
#		if defined NDEBUG
#			define PTHREAD_VERIFY(expr) (void)(expr)
#		else
#			include <assert.h>
#			define PTHREAD_VERIFY(expr) assert(!(expr))
#		endif

extern const pthread_mutex_t mutex_initializer;
extern const pthread_cond_t cond_initializer;

#	else
		/* Whatever caused HAVE_PTHREAD to be defined (configure script,
           usually) made a mistake if this is reached. */
		/* Maybe this should be a warning. */
#		error HAVE_PTHREAD is defined, but _POSIX_THREADS is not.
		/* #undef HAVE_PTHREAD */
#	endif
#endif

struct threadpool
{
/*	This is always the same as the count parameter fed to threadpool_create().
	Here's a neat trick: if the threadpool is zeroed out with a memset, and
	threadpool_create() is never called to create 0 threads, then
	threadpool::count can be used to determine if the threadpool object was
	ever initialized. */
	unsigned count;

	/* Copied from threadpool_class. No need for thread_create here, though. */
	size_t thread_size;
	void (*thread_run)(void *self);
	void (*thread_destroy)(void *self);

	void *serial_threads;

#if HAVE_PTHREAD
	pthread_mutex_t mutex;
	pthread_cond_t cond;

	/* Number of threads waiting for the startup signal. */
	unsigned parallel_pending;

	/* Number of threads still running. During startup, this is the index of the thread currently being initialized. */
	unsigned parallel_unfinished;

	pthread_t *parallel_threads;
#endif
};

/*
   The threadpool_* functions manage a group of threads (naturally).  Each
   thread owns an object described by a threadpool_class. When
   threadpool_run() is called, the specified func parameter is called on each
   thread in parallel. Sometime after calling threadpool_run(), call
   threadpool_wait(), which waits for each thread to return from
   threadpool_class::run().

   Note that thread 0 runs on the thread from which threadpool_run is called
   from, so if each thread has an equal workload, then when threadpool_run
   returns, the other threads will be finished or almost finished. Adding code
   between threadpool_run and threadpool_wait increases the odds that
   threadpool_wait won't actually have to wait at all -- which is nice.

   If the system does not provide threads, then these functions will fake it:
   everything will appear to work normally from the perspective of the caller,
   but when threadpool_run() is called, the "threads" are run synchronously;
   threadpool_wait() does nothing.
*/

struct threadpool_class
{
	/* Size of the thread private object. */
	size_t size;

/*	Create the thread private object. Called in sequence for each thread
	(effectively) from threadpool_create.
    self: A pointer to size bytes of memory, allocated to hold the thread
          object.
    pool: The threadpool object that owns all the threads. If the threadpool
          is nested in another struct, try GET_PARENT_OBJ.
    id:   The ID for the thread; numbering starts at zero and goes up by one
          for each thread.
    Return 0 on success. On failure, return a value from errno.h; this will
    be returned from threadpool_create. */
	int (*create)(void *self, struct threadpool *pool, unsigned id);

/*	Destroys the thread private object. Called in sequence (though not always
	the same sequence as create).  Warning: During shutdown, it is possible
	for destroy() to be called while other threads are still in
	threadpool_run(). */
	void (*destroy)(void *self);
};

/* Returns 0 on success, on failure can return ENOMEM, or any error code from
   threadpool_class.create. */
int threadpool_create(struct threadpool *self, const struct threadpool_class *cls, Display *dpy, unsigned count);
void threadpool_destroy(struct threadpool *self);

void threadpool_run(struct threadpool *self, void (*func)(void *));
void threadpool_wait(struct threadpool *self);

/* xlockmore: Removed unused io_thread code. */

#if HAVE_PTHREAD
#	define THREAD_DEFAULTS       "*useThreads: True",
#	define THREAD_DEFAULTS_XLOCK "*useThreads: True\n"
#	define THREAD_OPTIONS \
	{"-threads",    ".useThreads", XrmoptionNoArg, "True"}, \
	{"-no-threads", ".useThreads", XrmoptionNoArg, "False"},
#else
#	define THREAD_DEFAULTS
#	define THREAD_DEFAULTS_XLOCK
#	define THREAD_OPTIONS
#endif

/*
   If a variable 'member' is known to be a member (named 'member_name') of a
   struct (named 'struct_name'), then this can find a pointer to the struct
   that contains it.
*/
#define GET_PARENT_OBJ(struct_name, member_name, member) (struct_name *)((char *)member - offsetof(struct_name, member_name));

#endif
