/**************************************************************************
 *{@C
 *      Copyright:      1988-2023 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         Utilities
 *      Filename:       UT_Parallel.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    Functions for dealing with parallelism-related issues
 *                      in a machine-independent way:
 *                      - Simple locking routines.
 *                      - Handling of processes.
 *                      - Handling of threads.
 *
 *                      Note: Usage of threads can be 
 *                            controlled via the following preprocessor macros:
 *                            HAVE_THREADS and USE_THREADS.
 *                            HAVE_THREADS is a global property set in file
 *                            <UT_Compat.h> for each operating system / machine
 *                            combination indicating, if the system is in 
 *                            principle capable of using threads.
 *                            USE_THREADS is a user property set in file
 *                            <config.settings> to enable threaded image 
 *                            processing and ray-tracing. If USE_THREADS is not
 *                            defined, the algorithms swith to single-threaded
 *                            mode.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      UT_ThreadExec
 *
 **************************************************************************/

#include <stdio.h>

#include "UT_Compat.h"

#if defined (OS_IS_SYS5_UNIX)
    #include <sys/file.h>
    #include <sys/wait.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    #include <signal.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <unistd.h>
#endif

#if defined (OS_IS_WIN32)
    #include <io.h>
    #include <signal.h>
#endif

#include "UT_Const.h"
#include "UT_Swatch.h"
#include "UT_Memory.h"
#include "UT_Error.h"

#include "UT_Parallel.h"

/* Message strings */

#define MSG_NoConcCall     "Threading is not available on this machine"
#define MSG_ConcCall       "Threaded function call failed"

#if defined (HAVE_THREADS) && defined (USE_THREADS)

    #if defined (HAVE_PTHREADS)
        #include <pthread.h>

        typedef pthread_t UT_ThreadId;
    #endif

    /* A struct to store arguments for a child process */

    typedef struct {
        void    (*funct) (void *, Int32, Int32);
        void    *farg;          /* "Farg" argument to "funct" */
        Int32   n;              /* "N" argument to "funct" */
    } chargs;

    /* A struct to store information about a child process */

    typedef struct {
        Int32   i;              /* Child process index */
        chargs  *args;          /* Arguments passed to the child */
        UT_Bool valid;          /* The child process' identifier is valid */
        UT_ThreadId id;         /* The child process' identifier */
    } chinfo;


    /* The "main" function for all child processes created by
    "concurrent_call": Function "funct" is invoked as defined for
    UT_ThreadExec. The child process exits as soon as
    "funct" returns. */

    #if defined (HAVE_PTHREADS)
        static void* childmain (void *ptr)
        {
            chinfo *info = ptr;
    
            (*info->args->funct) (info->args->farg, info->args->n, info->i);
            pthread_exit (NULL);
            /* This statement will never be executed. Just to calm dowm 
             * some compilers. */
            return NULL;
        }
    #endif

    /* Concurrently infoke "n" incarnations of function "funct": "N" child
    processes are created. Each child thread invokes function "childmain",
    which then calls "funct". */

    static UT_Bool concurrent_call
            (void (*funct) (void *, Int32, Int32),
             void *farg, Int32 n, Int32 stacksize)
    {
        UT_Bool success;
        Int32   i;
        int     stat;
        chinfo  *info;
        chargs  args;
        #if defined (OS_IS_SYS5_UNIX) || \
            defined (OS_IS_WIN32)
            void (*INT_handler) (int),
                 (*TRAP_handler) (int),
                 (*EMT_handler) (int),
                 (*ILL_handler) (int),
                 (*FPE_handler) (int);
        #else
            int (*INT_handler) (int),
                (*TRAP_handler) (int),
                (*EMT_handler) (int),
                (*ILL_handler) (int),
                (*FPE_handler) (int);
        #endif
        #if defined (HAVE_PTHREADS)
            pthread_attr_t attr;
        #endif
    
        /* Allocate and initialize a "chinfo" struct for each child process,
        so that arguments can be passed individually to each child process. */

        args.funct = funct;
        args.farg = farg;
        args.n = n;
        if (!(info = UT_MemTempArray (n, chinfo))) {
            return UT_False;
        }
        for (i = 0; i < n; ++i) {
            info[i].i = i;
            info[i].args = &args;
            info[i].valid = UT_True;
        }

        /* Disable the interrupt and floating point exception signals;
        save pointers to the previous signal handlers so they can be
        restored later. */

        INT_handler  = signal (SIGINT,  SIG_IGN);
        ILL_handler  = signal (SIGILL,  SIG_IGN);
        FPE_handler  = signal (SIGFPE,  SIG_IGN);
        #if !defined (WIN32)
            TRAP_handler = signal (SIGTRAP, SIG_IGN);
        #endif
        #if !defined (Linux) && !defined (WIN32)
            EMT_handler = signal (SIGEMT, SIG_IGN);
        #endif

        #if defined (HAVE_PTHREADS)
            pthread_attr_init(&attr);
            pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE);

            pthread_attr_setstacksize (&attr, stacksize);

            /* Start the child processes. */

            success = UT_True;
            for (i = 0; i < n; ++i) {
                stat = pthread_create (&info[i].id, &attr,
                                       childmain, (void *)&info[i]);
                if (0 != stat) {
                    info[i].valid = UT_False;
                    success = UT_False;
                    break;
                }
            }

            /* Wait for the children to terminate. */

            for (i = 0; i < n; ++i) {
                if (info[i].valid) {
                    stat = pthread_join (info[i].id, NULL);
                    if (0 != stat) {
                        success = UT_False;
                        break;
                    }
                }
            }

            pthread_attr_destroy (&attr);
        #endif

        /* Restore the signal handlers which were installed
        before we ran the child processes. */

        signal (SIGINT, INT_handler);
        signal (SIGILL, ILL_handler);
        signal (SIGFPE, FPE_handler);
        #if !defined (WIN32)
            signal (SIGTRAP, TRAP_handler);
        #endif
        #if !defined (Linux) && !defined (WIN32)
            signal (SIGEMT, EMT_handler);
        #endif

        if (!success) {
            UT_ErrSetNum (UT_ErrFromOs (), MSG_ConcCall);
        }
        return success;
    }

#endif

/***************************************************************************
 *[@e
 *      Name:           UT_ThreadExec
 *
 *      Usage:          Concurrently invokes multiple copies of a function.
 *
 *      Synopsis:       UT_Bool UT_ThreadExec
 *                              (void (*funct) (void *, Int32, Int32),
 *                              void *farg, Int32 n, Int32 stacksize)
 *
 *      Description:    "N" incarnations of function "funct" are invoked
 *                      simultaneously and run in parallel, possibly on
 *                      different processors. All incarnations share the
 *                      same virtual address space, but each incarnation
 *                      has its own runtime stack for auto variables.
 *                      UT_ThreadExec returns after all incarnations
 *                      of "funct" have terminated.
 *
 *                      Function "funct" should be declared as
 *
 *                              void funct (void *farg, Int32 n, Int32 i)
 *
 *                      Upon invocation, "funct" will receive three arguments:
 *                      "Farg" and "n" are copies of the "farg" and "n"
 *                      arguments passed to UT_ThreadExec. "Farg" and
 *                      "n" are passed to all incarnations of "funct". "Farg"
 *                      can be used to pass data to "funct". "N" can be used
 *                      by "funct" to determine how many other incarnations
 *                      are running at the same time.
 *                      Each incarnation is assigned a unique number in the
 *                      range from 0 to n-1, which is passed to "funct" in "i".
 *
 *                      "Stackksize" bytes are allocated for the auto variable
 *                      stack for each incarnation of "funct".
 *
 *      Return value:   UT_ThreadExec returns "UT_True" if it could
 *                      successfully start "n" incarnations of "funct",
 *                      or "UT_False" if an error occurred while invoking
 *                      "funct".
 *
 *                      Notes:
 *
 *                      - Signals which may be caused by invalid floating
 *                        point operations (SIGFPE, SIGEMT, SIGILL and
 *                        SIGTRAP on Unix type operating systems), and the
 *                        signal generated when the user presses the break
 *                        key (SIGINT on Unix systems), are ignored while
 *                        "funct" is running concurrently.
 *
 *                      - Most functions in the C standard I/O library, and
 *                        many of the poSoft library routines are not
 *                        written to be concurrently re-entrant, so these
 *                        routines should not be invoked by "funct".
 *                        In particular, "funct" must not invoke any of the
 *                        functions from UT_Error and UT_Memory libraries.
 *
 *                      - Some operating systems cannot create multiple program
 *                        execution threads which share the same address space.
 *                        On these systems, UT_ThreadExec always returns
 *                        "UT_False". For systems supporting UT_ThreadExec,
 *                        a C preprocessor constant, HAVE_THREADS, is
 *                        defined in file <UT_Compat.h>.
 *
 *                      - You should always use the macro UT_THREAD_EXEC
 *                        defined in file UT_Parallel.h. If your platform does
 *                        not support threads, the code will be executed as a
 *                        standard function call.
 *
 *                      - If setting parameter n to zero, the code will be
 *                        executed as a standard function call.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool UT_ThreadExec (void (*funct) (void *, Int32, Int32),
                       void *farg, Int32 n, Int32 stacksize)
{
    #if defined (HAVE_THREADS) && defined (USE_THREADS)
        UT_MemState memstate;
        UT_Bool    success;

        if (n == 0) {
            (* funct) (farg, 0, 0);
            success = UT_True;
        } else {
            memstate = UT_MemRemember ();
            success = concurrent_call (funct, farg, n, stacksize);
            UT_MemRestore (memstate);
        }
        return success;
    #else
        UT_ErrSetNum (UT_ErrNotSupported, MSG_NoConcCall);
        return UT_False;
    #endif
}
