/*
    Copyright (c) 2014-2015 Intel Corporation.  All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
      * Neither the name of Intel Corporation nor the names of its
        contributors may be used to endorse or promote products derived
        from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <set>
#include <map>
#include <queue>

#include "coi_host.h"

#include "coi_version_asm.h"

#define CYCLE_FREQUENCY     1000000000

enum buffer_t
{
  BUFFER_NORMAL,
  BUFFER_MEMORY
};

struct Engine
{
  COI_ISA_TYPE type;
  uint32_t index;
  char *dir;
};

struct Function
{
  void *ptr;
  uint32_t num_buffers;
  uint64_t *bufs_size;
  void * *bufs_data_target;
  uint16_t misc_data_len;
  void *misc_data;
  uint16_t return_value_len;
  void *return_value;
  COIEVENT completion_event;
};

struct Callback
{
  COI_EVENT_CALLBACK ptr;
  const void *data;
};

struct Process
{
  pid_t pid;
  int pipe_host2tgt;
  int pipe_tgt2host;
  Engine *engine;
  void **functions;
};

struct Pipeline
{
  pthread_t thread;
  bool destroy;
  bool is_destroyed;
  char *pipe_host2tgt_path;
  char *pipe_tgt2host_path;
  int pipe_host2tgt;
  int pipe_tgt2host;
  std::queue<Function> queue;
  Process *process;
};

struct Buffer
{
  buffer_t type;
  char *name;
  int fd;
  int fd_target;
  uint64_t size;
  void *data;
  void *data_target;
  Process *process;
};


/* Environment variables.  */
extern char **environ;

/* List of directories for removing on exit.  */
static char **tmp_dirs;
static unsigned tmp_dirs_num;

/* Number of emulated MIC engines.  */
static long num_engines;

/* Number of the last COI pipeline.  */
static uint32_t max_pipeline_num;

/* Set of undestroyed pipelines.  */
static std::set<Pipeline *> pipelines;

/* Number of the last COI event, the event #0 is always signalled.  */
static uint64_t max_event_num = 1;

/* Set of created COI events, which are not signalled.  */
static std::set<uint64_t> non_signalled_events;

/* Set of COI events, which encountered errors.  */
static std::map<uint64_t, COIRESULT> errored_events;

/* Set of registered callbacks, indexed by event number.  */
static std::map<uint64_t, Callback> callbacks;

/* Mutex to sync parallel execution.  */
static pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;


static COIRESULT
read_long_env (const char *env_name, long *var, long var_default)
{
  char *str = getenv (env_name);
  char *s;

  if (!str || *str == '\0')
    *var = var_default;
  else
    {
      errno = 0;
      *var = strtol (str, &s, 0);
      if (errno != 0 || s == str || *s != '\0')
	COIERROR ("Variable %s has invalid value.", env_name);
    }

  return COI_SUCCESS;
}

__attribute__((constructor))
static void
init ()
{
  if (read_long_env (OFFLOAD_EMUL_NUM_ENV, &num_engines, 1) == COI_ERROR)
    exit (0);
}


/* Helper function for directory removing.  */
static COIRESULT remove_directory (char *path)
{
  char *file;
  struct dirent *entry;
  struct stat statfile;
  DIR *dir = opendir (path);
  if (dir == NULL)
    COIERROR ("Cannot open directory %s.", dir);

  while (entry = readdir (dir))
    {
      if (!strcmp (entry->d_name, ".") || !strcmp (entry->d_name, ".."))
	continue;

      MALLOC (char *, file, strlen (path) + strlen (entry->d_name) + 2);
      sprintf (file, "%s/%s", path, entry->d_name);

      if (stat (file, &statfile) < 0)
	COIERROR ("Cannot retrieve information about file %s.", file);

      if (S_ISDIR (statfile.st_mode))
	{
	  if (remove_directory (file) == COI_ERROR)
	    return COI_ERROR;
	}
      else
	{
	  if (unlink (file) < 0)
	    COIERROR ("Cannot unlink file %s.", file);
	}

      free (file);
    }

  if (closedir (dir) < 0)
    COIERROR ("Cannot close directory %s.", path);
  if (rmdir (path) < 0)
    COIERROR ("Cannot remove directory %s.", path);

  return COI_SUCCESS;
}

__attribute__((destructor))
static void
cleanup ()
{
  for (unsigned i = 0; i < tmp_dirs_num; i++)
    {
      remove_directory (tmp_dirs[i]);
      free (tmp_dirs[i]);
    }
  free (tmp_dirs);
}

static COIRESULT
start_critical_section ()
{
  if (pthread_mutex_lock (&mutex) != 0)
    COIERROR ("Cannot lock mutex.");
  return COI_SUCCESS;
}

static COIRESULT
finish_critical_section ()
{
  if (pthread_mutex_unlock (&mutex) != 0)
    COIERROR ("Cannot unlock mutex.");
  return COI_SUCCESS;
}

static bool
pipeline_is_destroyed (const Pipeline *pipeline)
{
  start_critical_section ();
  bool res = pipeline->is_destroyed;
  finish_critical_section ();
  return res;
}

static void
maybe_invoke_callback (const COIEVENT event, const COIRESULT result)
{
  std::map<uint64_t, Callback>::iterator cb = callbacks.find (event.opaque[0]);

  if (cb != callbacks.end ())
    {
      Callback callback = cb->second;
      callback.ptr (event, result, callback.data);
      callbacks.erase (cb);
    }
}

static void
signal_event (const COIEVENT event, const COIRESULT result)
{
  if (result != COI_SUCCESS)
    errored_events.insert (std::pair <uint64_t, COIRESULT> (event.opaque[0],
							    result));
  non_signalled_events.erase (event.opaque[0]);

  maybe_invoke_callback (event, result);
}

static COIRESULT
get_event_result (const COIEVENT event)
{
  COIRESULT res = COI_SUCCESS;

  std::map<uint64_t, COIRESULT>::iterator ee
    = errored_events.find (event.opaque[0]);

  if (ee != errored_events.end ())
    res = ee->second;

  return res;
}


extern "C"
{

COIRESULT
SYMBOL_VERSION (COIBufferCopy, 1) (COIBUFFER in_DestBuffer,
				   COIBUFFER in_SourceBuffer,
				   uint64_t in_DestOffset,
				   uint64_t in_SourceOffset,
				   uint64_t in_Length,
				   COI_COPY_TYPE in_Type,
				   uint32_t in_NumDependencies,
				   const COIEVENT *in_pDependencies,  // Ignored
				   COIEVENT *out_pCompletion)
{
  COITRACE ("COIBufferCopy");

  /* Features of liboffloadmic.  */
  assert (in_DestBuffer != NULL);
  assert (in_SourceBuffer != NULL);
  assert (in_Type == COI_COPY_UNSPECIFIED);
  assert (in_NumDependencies == 0);

  /* Convert input arguments.  */
  Buffer *dest = (Buffer *) in_DestBuffer;
  Buffer *source = (Buffer *) in_SourceBuffer;

  start_critical_section ();

  /* Map buffers if needed.  */
  if (dest->data == 0 && dest->type == BUFFER_NORMAL)
    if (COIBufferMap (in_DestBuffer, 0, dest->size, (COI_MAP_TYPE) 0,
		      0, 0, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;
  if (source->data == 0 && source->type == BUFFER_NORMAL)
    if (COIBufferMap (in_SourceBuffer, 0, source->size, (COI_MAP_TYPE) 0,
		      0, 0, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;

  /* Copy data.  */
  if (source->data != 0 && dest->data != 0)
    memcpy ((void *) ((uintptr_t) dest->data + in_DestOffset),
	    (void *) ((uintptr_t) source->data + in_SourceOffset), in_Length);
  else
    {
      assert (dest->process == source->process);

      Buffer *buffer;
      cmd_t cmd = CMD_BUFFER_COPY;

      /* Create intermediary buffer.  */
      if (COIBufferCreate (in_Length, COI_BUFFER_NORMAL, 0, 0, 1,
			   (COIPROCESS*) &dest->process,
			   (COIBUFFER *) &buffer) == COI_ERROR)
	return COI_ERROR;

      int pipe_host2tgt = dest->process->pipe_host2tgt;
      int pipe_tgt2host = dest->process->pipe_tgt2host;

      /* Copy from source to intermediary buffer.  */
      if (source->data == 0)
	{
	  assert (source->data_target != 0);

	  /* Send data to target.  */
	  WRITE (pipe_host2tgt, &cmd, sizeof (cmd_t));
	  WRITE (pipe_host2tgt, &buffer->data_target, sizeof (void *));
	  WRITE (pipe_host2tgt, &source->data_target, sizeof (void *));
	  WRITE (pipe_host2tgt, &buffer->size, sizeof (uint64_t));

	  /* Receive data from target.  */
	  READ (pipe_tgt2host, &cmd, sizeof (cmd_t));
	}
      else
	{
	  if (COIBufferCopy ((COIBUFFER) buffer, in_SourceBuffer, 0,
			     in_SourceOffset, in_Length, in_Type, 0, 0, 0)
	      == COI_ERROR)
	    return COI_ERROR;
	}

      /* Copy from intermediary buffer to dest.  */
      if (dest->data == 0)
	{
	  assert (dest->data_target != 0);

	  /* Send data to target.  */
	  WRITE (pipe_host2tgt, &cmd, sizeof (cmd_t));
	  WRITE (pipe_host2tgt, &dest->data_target, sizeof (void *));
	  WRITE (pipe_host2tgt, &buffer->data_target, sizeof (void *));
	  WRITE (pipe_host2tgt, &buffer->size, sizeof (uint64_t));

	  /* Receive data from target.  */
	  READ (pipe_tgt2host, &cmd, sizeof (cmd_t));
	}
      else
	{
	  if (COIBufferCopy (in_DestBuffer, (COIBUFFER) buffer, in_DestOffset,
			     0, in_Length, in_Type, 0, 0, 0) == COI_ERROR)
	    return COI_ERROR;
	}

      /* Unmap on target and destroy intermediary buffer.  */
      if (COIBufferDestroy ((COIBUFFER) buffer) == COI_ERROR)
	return COI_ERROR;
    }

  /* Unmap buffers if needed.  */
  if (dest->type == BUFFER_NORMAL)
    if (COIBufferUnmap ((COIMAPINSTANCE) dest, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;
  if (source->type == BUFFER_NORMAL)
    if (COIBufferUnmap ((COIMAPINSTANCE) source, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;

  finish_critical_section ();

  if (out_pCompletion)
    out_pCompletion->opaque[0] = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferCreate, 1) (uint64_t in_Size,
				     COI_BUFFER_TYPE in_Type,
				     uint32_t in_Flags,
				     const void *in_pInitData,
				     uint32_t in_NumProcesses,
				     const COIPROCESS *in_pProcesses,
				     COIBUFFER *out_pBuffer)
{
  COITRACE ("COIBufferCreate");

  char *shm_name;
  int shm_fd;
  const int ullong_max_len = 20;

  /* Features of liboffloadmic.  */
  assert (in_Type == COI_BUFFER_NORMAL);
  assert ((in_Flags & COI_SINK_MEMORY) == 0);
  assert ((in_Flags & COI_SAME_ADDRESS_SINKS) == 0);
  assert ((in_Flags & COI_SAME_ADDRESS_SINKS_AND_SOURCE) == 0);
  assert (in_pInitData == NULL);
  assert (in_NumProcesses == 1);
  assert (in_pProcesses != NULL);
  assert (out_pBuffer != NULL);

  /* Create shared memory with an unique name.  */
  MALLOC (char *, shm_name, strlen (SHM_NAME) + ullong_max_len + 1);
  for (unsigned long long i = 0; i >= 0; i++)
    {
      sprintf (shm_name, SHM_NAME "%lu", i);
      shm_fd = shm_open (shm_name, O_CLOEXEC | O_CREAT | O_EXCL | O_RDWR,
			 S_IRUSR | S_IWUSR);
      if (shm_fd > 0)
	break;
    }
  if (ftruncate (shm_fd, in_Size) < 0)
    COIERROR ("Cannot truncate shared memory file.");

  /* Create buffer.  */
  Buffer *buf = new Buffer;
  buf->data = 0;
  buf->fd = shm_fd;
  buf->process = (Process *) in_pProcesses[0];
  buf->size = in_Size;
  buf->type = BUFFER_NORMAL;
  STRDUP (buf->name, shm_name);

  /* Map buffer on target.  */
  size_t len = strlen (buf->name) + 1;

  start_critical_section ();

  /* Send data to target.  */
  const cmd_t cmd = CMD_BUFFER_MAP;
  int pipe_host2tgt = buf->process->pipe_host2tgt;
  WRITE (pipe_host2tgt, &cmd, sizeof (cmd_t));
  WRITE (pipe_host2tgt, &len, sizeof (size_t));
  WRITE (pipe_host2tgt, buf->name, len);
  WRITE (pipe_host2tgt, &buf->size, sizeof (uint64_t));

  /* Receive data from target.  */
  int pipe_tgt2host = buf->process->pipe_tgt2host;
  READ (pipe_tgt2host, &buf->fd_target, sizeof (int));
  READ (pipe_tgt2host, &buf->data_target, sizeof (void *));

  finish_critical_section ();

  /* Prepare output arguments.  */
  *out_pBuffer = (COIBUFFER) buf;

  /* Clean up.  */
  free (shm_name);

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferCreateFromMemory, 1) (uint64_t in_Size,
					       COI_BUFFER_TYPE in_Type,
					       uint32_t in_Flags,
					       void *in_Memory,
					       uint32_t in_NumProcesses,
					       const COIPROCESS *in_pProcesses,
					       COIBUFFER *out_pBuffer)
{
  COITRACE ("COIBufferCreateFromMemory");

  /* Features of liboffloadmic.  */
  assert (in_Type == COI_BUFFER_NORMAL);
  assert ((in_Flags & COI_SAME_ADDRESS_SINKS) == 0);
  assert ((in_Flags & COI_SAME_ADDRESS_SINKS_AND_SOURCE) == 0);
  assert (in_NumProcesses == 1);
  assert (in_pProcesses != NULL);
  assert (out_pBuffer != NULL);

  /* Create buffer.  */
  Buffer *buf = new Buffer;
  buf->data = (in_Flags & COI_SINK_MEMORY) == 0 ? in_Memory : 0;
  buf->data_target = (in_Flags & COI_SINK_MEMORY) != 0 ? in_Memory : 0;
  buf->process = (Process *) in_pProcesses[0];
  buf->size = in_Size;
  buf->type = BUFFER_MEMORY;

  /* Prepare output argument.  */
  *out_pBuffer = (COIBUFFER) buf;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferDestroy, 1) (COIBUFFER in_Buffer)
{
  COITRACE ("COIBufferDestroy");

  cmd_t cmd = CMD_BUFFER_UNMAP;

  assert (in_Buffer != NULL);

  /* Convert input arguments.  */
  Buffer *buf = (Buffer *) in_Buffer;

  /* Unmap buffer on host.  */
  if (buf->data != 0 && buf->type == BUFFER_NORMAL)
    if (COIBufferUnmap ((COIMAPINSTANCE) in_Buffer, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;

  /* Unmap buffer on target.  */
  if (buf->data_target != 0)
    {
      start_critical_section ();

      /* Send data to target.  */
      int pipe_host2tgt = buf->process->pipe_host2tgt;
      WRITE (pipe_host2tgt, &cmd, sizeof (cmd_t));
      WRITE (pipe_host2tgt, &buf->fd_target, sizeof (int));
      WRITE (pipe_host2tgt, &buf->data_target, sizeof (void *));
      WRITE (pipe_host2tgt, &buf->size, sizeof (uint64_t));

      /* Receive data from target.  */
      READ (buf->process->pipe_tgt2host, &cmd, sizeof (cmd_t));

      finish_critical_section ();
    }

  /* Unlink shared memory.  */
  if (buf->type == BUFFER_NORMAL)
    {
      if (close (buf->fd) < 0)
	COIERROR ("Cannot close shared memory file.");
      if (shm_unlink (buf->name) < 0)
	COIERROR ("Cannot unlink shared memory.");
      free (buf->name);
    }

  /* Clean up.  */
  delete buf;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferGetSinkAddress, 1) (COIBUFFER in_Buffer,
					     uint64_t *out_pAddress)
{
  COITRACE ("COIBufferGetSinkAddress");

  assert (in_Buffer != NULL);
  assert (out_pAddress != NULL);

  /* Convert input arguments.  */
  Buffer *buf = (Buffer *) in_Buffer;

  /* Here should come BUFFER_NORMAL buffer.  */
  assert (buf->type == BUFFER_NORMAL);

  /* Prepare output argument.  */
  *out_pAddress = (uint64_t) buf->data_target;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferMap, 1) (COIBUFFER in_Buffer,
				  uint64_t in_Offset,
				  uint64_t in_Length,		    // Ignored
				  COI_MAP_TYPE in_Type,		    // Ignored
				  uint32_t in_NumDependencies,
				  const COIEVENT *in_pDependencies, // Ignored
				  COIEVENT *out_pCompletion,
				  COIMAPINSTANCE *out_pMapInstance,
				  void **out_ppData)
{
  COITRACE ("COIBufferMap");

  /* Features of liboffloadmic.  */
  assert (in_Offset == 0);
  assert (in_NumDependencies == 0);

  /* Convert input arguments.  */
  Buffer *buf = (Buffer *) in_Buffer;

  /* Only BUFFER_NORMAL buffers should come here.  */
  assert (buf->type == BUFFER_NORMAL);

  /* Map shared memory.  */
  buf->data = mmap (NULL, buf->size, PROT_READ | PROT_WRITE,
		    MAP_SHARED, buf->fd, 0);
  if (buf->data == NULL)
    COIERROR ("Cannot map shared memory.");

  /* Prepare output arguments.  */
  if (out_pMapInstance != 0)
    *out_pMapInstance = (COIMAPINSTANCE) buf;
  if (out_ppData != 0)
    *out_ppData = buf->data;

  if (out_pCompletion)
    out_pCompletion->opaque[0] = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferRead, 1) (COIBUFFER in_SourceBuffer,
				   uint64_t in_Offset,
				   void *in_pDestData,
				   uint64_t in_Length,
				   COI_COPY_TYPE in_Type,
				   uint32_t in_NumDependencies,
				   const COIEVENT *in_pDependencies,  // Ignored
				   COIEVENT *out_pCompletion)
{
  COITRACE ("COIBufferRead");

  /* Features of liboffloadmic.  */
  assert (in_pDestData != NULL);
  assert (in_Type == COI_COPY_UNSPECIFIED);
  assert (in_NumDependencies == 0);

  /* Convert input arguments.  */
  Buffer *buf = (Buffer *) in_SourceBuffer;

  start_critical_section ();

  /* Map buffers if needed.  */
  if (buf->data == 0 && buf->type == BUFFER_NORMAL)
    if (COIBufferMap (in_SourceBuffer, 0, buf->size, (COI_MAP_TYPE) 0, 0, 0, 0,
		      0, 0) == COI_ERROR)
      return COI_ERROR;

  /* Copy data.  */
  memcpy (in_pDestData, (void *) ((uintptr_t) buf->data + in_Offset),
	  in_Length);

  /* Unmap buffers if needed.  */
  if (buf->type == BUFFER_NORMAL)
    if (COIBufferUnmap ((COIMAPINSTANCE) buf, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;

  finish_critical_section ();

  if (out_pCompletion)
    out_pCompletion->opaque[0] = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferSetState, 1) (COIBUFFER in_Buffer,		  // Ignored
				       COIPROCESS in_Process,		  // Ignored
				       COI_BUFFER_STATE in_State,	  // Ignored
				       COI_BUFFER_MOVE_FLAG in_DataMove,
				       uint32_t in_NumDependencies,
				       const COIEVENT *in_pDependencies,  // Ignored
				       COIEVENT *out_pCompletion)
{
  COITRACE ("COIBufferSetState");

  /* Features of liboffloadmic.  */
  assert (in_DataMove == COI_BUFFER_NO_MOVE);
  assert (in_NumDependencies == 0);

  /* Looks like we have nothing to do here.  */

  if (out_pCompletion)
    out_pCompletion->opaque[0] = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferUnmap, 1) (COIMAPINSTANCE in_MapInstance,
				    uint32_t in_NumDependencies,
				    const COIEVENT *in_pDependencies, // Ignored
				    COIEVENT *out_pCompletion)
{
  COITRACE ("COIBufferUnmap");

  /* Features of liboffloadmic.  */
  assert (in_MapInstance != NULL);
  assert (in_NumDependencies == 0);

  /* Convert input arguments.  */
  Buffer *buffer = (Buffer *) in_MapInstance;

  /* Only BUFFER_NORMAL buffers should come here.  */
  assert (buffer->type == BUFFER_NORMAL);

  /* Unmap shared memory.  */
  if (munmap (buffer->data, buffer->size) < 0)
    COIERROR ("Cannot unmap shared memory.");

  buffer->data = 0;

  if (out_pCompletion)
    out_pCompletion->opaque[0] = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIBufferWrite, 1) (COIBUFFER in_DestBuffer,
				    uint64_t in_Offset,
				    const void *in_pSourceData,
				    uint64_t in_Length,
				    COI_COPY_TYPE in_Type,
				    uint32_t in_NumDependencies,
				    const COIEVENT *in_pDependencies, // Ignored
				    COIEVENT *out_pCompletion)
{
  COITRACE ("COIBufferWrite");

  /* Features of liboffloadmic.  */
  assert (in_DestBuffer != NULL);
  assert (in_pSourceData != NULL);
  assert (in_Type == COI_COPY_UNSPECIFIED);
  assert (in_NumDependencies == 0);

  /* Convert input arguments.  */
  Buffer *buf = (Buffer *) in_DestBuffer;

  start_critical_section ();

  /* Map buffers if needed.  */
  if (buf->data == 0 && buf->type == BUFFER_NORMAL)
    if (COIBufferMap (in_DestBuffer, 0, buf->size, (COI_MAP_TYPE) 0, 0, 0, 0, 0,
		      0) == COI_ERROR)
      return COI_ERROR;

  /* Copy data.  */
  memcpy ((void *) ((uintptr_t) buf->data + in_Offset), in_pSourceData,
	  in_Length);

  /* Unmap buffers if needed.  */
  if (buf->type == BUFFER_NORMAL)
    if (COIBufferUnmap ((COIMAPINSTANCE) buf, 0, 0, 0) == COI_ERROR)
      return COI_ERROR;

  finish_critical_section ();

  if (out_pCompletion)
    out_pCompletion->opaque[0] = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIEngineGetCount, 1) (COI_ISA_TYPE isa,
				       uint32_t *count)
{
  COITRACE ("COIEngineGetCount");

  /* Features of liboffloadmic.  */
  assert (isa == COI_ISA_MIC);
  assert (count != NULL);

  /* Prepare output arguments.  */
  *count = num_engines;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIEngineGetHandle, 1) (COI_ISA_TYPE in_ISA,
					uint32_t in_EngineIndex,
					COIENGINE *out_pEngineHandle)
{
  COITRACE ("COIEngineGetHandle");

  /* Features of liboffloadmic.  */
  assert (in_ISA == COI_ISA_MIC);
  assert (out_pEngineHandle != NULL);

  /* Check engine index.  */
  if (in_EngineIndex >= num_engines)
    COIERROR ("Wrong engine index.");

  /* Create engine handle.  */
  Engine *engine = new Engine;
  engine->dir = NULL;
  engine->index = in_EngineIndex;
  engine->type = in_ISA;

  /* Prepare output argument.  */
  *out_pEngineHandle = (COIENGINE) engine;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIEventWait, 1) (uint16_t in_NumEvents,
				  const COIEVENT *in_pEvents,
				  int32_t in_TimeoutMilliseconds,
				  uint8_t in_WaitForAll,
				  uint32_t *out_pNumSignaled,
				  uint32_t *out_pSignaledIndices)
{
  COITRACE ("COIEventWait");

  /* Features of liboffloadmic.  */
  assert (in_pEvents != NULL);
  assert (in_TimeoutMilliseconds == 0 || in_TimeoutMilliseconds == -1);
  assert (in_WaitForAll == 1);
  assert (out_pNumSignaled == NULL);
  assert (out_pSignaledIndices == NULL);

  if (in_TimeoutMilliseconds == 0)
    {
      /* If some event is not signalled, return timeout error.  */
      for (uint16_t i = 0; i < in_NumEvents; i++)
	if (non_signalled_events.count (in_pEvents[i].opaque[0]) > 0)
	  return COI_TIME_OUT_REACHED;
	else
	  {
	    /* If the event signalled with an error, return that error.  */
	    start_critical_section ();
	    COIRESULT res = get_event_result (in_pEvents[i]);
	    finish_critical_section ();
	    if (res != COI_SUCCESS)
	      return res;
	  }
    }
  else
    {
      /* Wait indefinitely for all events.  */
      for (uint16_t i = 0; i < in_NumEvents; i++)
	{
	  while (non_signalled_events.count (in_pEvents[i].opaque[0]) > 0)
	    usleep (1000);

	  /* If the event signalled with an error, return that error.  */
	  start_critical_section ();
	  COIRESULT res = get_event_result (in_pEvents[i]);
	  finish_critical_section ();
	  if (res != COI_SUCCESS)
	    return res;
	}
    }

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIEventRegisterCallback, 1) (const COIEVENT in_Event,
					      COI_EVENT_CALLBACK in_Callback,
					      const void *in_UserData,
					      const uint64_t in_Flags)
{
  COITRACE ("COIEventRegisterCallback");

  /* Features of liboffloadmic.  */
  assert (in_Callback != NULL);
  assert (in_UserData != NULL);
  assert (in_Flags == 0);

  start_critical_section ();
  if (non_signalled_events.count (in_Event.opaque[0]) == 0)
    {
      /* If the event is already signalled, invoke the callback immediately.  */
      COIRESULT res = get_event_result (in_Event);
      in_Callback (in_Event, res, in_UserData);
    }
  else
    {
      Callback callback;
      callback.ptr = in_Callback;
      callback.data = in_UserData;
      callbacks.insert (std::pair <uint64_t, Callback> (in_Event.opaque[0],
							callback));
    }
  finish_critical_section ();

  return COI_SUCCESS;
}


/* The start routine for the COI pipeline thread.  */

static void *
pipeline_thread_routine (void *in_Pipeline)
{
  /* Convert input arguments.  */
  Pipeline *pipeline = (Pipeline *) in_Pipeline;

  /* Open pipes.  */
  pipeline->pipe_host2tgt
    = open (pipeline->pipe_host2tgt_path, O_CLOEXEC | O_WRONLY);
  if (pipeline->pipe_host2tgt < 0)
    COIERRORN ("Cannot open host-to-target pipe.");
  pipeline->pipe_tgt2host
    = open (pipeline->pipe_tgt2host_path, O_CLOEXEC | O_RDONLY);
  if (pipeline->pipe_tgt2host < 0)
    COIERRORN ("Cannot open target-to-host pipe.");

  free (pipeline->pipe_host2tgt_path);
  free (pipeline->pipe_tgt2host_path);
  pipeline->pipe_host2tgt_path = NULL;
  pipeline->pipe_tgt2host_path = NULL;

  while (!pipeline->destroy)
    if (pipeline->queue.empty ())
      usleep (1000);
    else
      {
	Function func = pipeline->queue.front ();
	start_critical_section ();
	pipeline->queue.pop ();
	finish_critical_section ();

	/* Send data to target.  */
	cmd_t cmd = CMD_PIPELINE_RUN_FUNCTION;
	WRITEN (pipeline->pipe_host2tgt, &cmd, sizeof (cmd_t));
	WRITEN (pipeline->pipe_host2tgt, &func.ptr, sizeof (void *));
	WRITEN (pipeline->pipe_host2tgt, &func.num_buffers, sizeof (uint32_t));
	for (uint32_t i = 0; i < func.num_buffers; i++)
	  {
	    WRITEN (pipeline->pipe_host2tgt, &func.bufs_size[i],
		    sizeof (uint64_t));
	    WRITEN (pipeline->pipe_host2tgt, &func.bufs_data_target[i],
		    sizeof (void *));
	  }
	WRITEN (pipeline->pipe_host2tgt, &func.misc_data_len,
		sizeof (uint16_t));
	if (func.misc_data_len > 0)
	  WRITEN (pipeline->pipe_host2tgt, func.misc_data, func.misc_data_len);
	WRITEN (pipeline->pipe_host2tgt, &func.return_value_len,
		sizeof (uint16_t));

	delete [] func.bufs_size;
	delete [] func.bufs_data_target;

	/* Receive data from target.  Wait for target function to complete,
	   whether it has any data to return or not.  */
	bool has_return_value = func.return_value_len > 0;
	int ret_len
	  = read (pipeline->pipe_tgt2host,
		  has_return_value ? func.return_value : &cmd,
		  has_return_value ? func.return_value_len : sizeof (cmd_t));
	if (ret_len == 0)
	  {
	    start_critical_section ();
	    signal_event (func.completion_event, COI_PROCESS_DIED);
	    pipeline->is_destroyed = true;
	    finish_critical_section ();
	    return NULL;
	  }
	else if (ret_len != (has_return_value ? func.return_value_len
					      : sizeof (cmd_t)))
	  COIERRORN ("Cannot read from pipe.");

	start_critical_section ();
	signal_event (func.completion_event, COI_SUCCESS);
	finish_critical_section ();
      }

  /* Send data to target.  */
  const cmd_t cmd = CMD_PIPELINE_DESTROY;
  WRITEN (pipeline->pipe_host2tgt, &cmd, sizeof (cmd_t));

  /* Close pipes.  */
  if (close (pipeline->pipe_host2tgt) < 0)
    COIERRORN ("Cannot close host-to-target pipe.");
  if (close (pipeline->pipe_tgt2host) < 0)
    COIERRORN ("Cannot close target-to-host pipe.");

  start_critical_section ();
  pipeline->is_destroyed = true;
  finish_critical_section ();
  return NULL;
}


COIRESULT
SYMBOL_VERSION (COIPipelineCreate, 1) (COIPROCESS in_Process,
				       COI_CPU_MASK in_Mask,
				       uint32_t in_StackSize,	    // Ignored
				       COIPIPELINE *out_pPipeline)
{
  COITRACE ("COIPipelineCreate");

  /* Features of liboffloadmic.  */
  assert (in_Process != NULL);
  assert (in_Mask == 0);
  assert (out_pPipeline != NULL);

  /* Convert input arguments.  */
  Process *proc = (Process *) in_Process;

  start_critical_section ();

  /* Create pipeline handle.  */
  Pipeline *pipeline = new Pipeline;
  pipeline->destroy = false;
  pipeline->is_destroyed = false;
  pipeline->process = proc;
  pipelines.insert (pipeline);

  /* Create pipes.  */
  uint32_t pipeline_num = max_pipeline_num++;
  char *eng_dir = pipeline->process->engine->dir;
  MALLOC (char *, pipeline->pipe_host2tgt_path,
	  strlen (eng_dir) + sizeof (PIPE_HOST2TGT_NAME "0000000000"));
  MALLOC (char *, pipeline->pipe_tgt2host_path,
	  strlen (eng_dir) + sizeof (PIPE_TGT2HOST_NAME "0000000000"));
  sprintf (pipeline->pipe_host2tgt_path, "%s" PIPE_HOST2TGT_NAME "%010d",
	   eng_dir, pipeline_num);
  sprintf (pipeline->pipe_tgt2host_path, "%s" PIPE_TGT2HOST_NAME "%010d",
	   eng_dir, pipeline_num);
  if (mkfifo (pipeline->pipe_host2tgt_path, S_IRUSR | S_IWUSR) < 0)
    COIERROR ("Cannot create pipe %s.", pipeline->pipe_host2tgt_path);
  if (mkfifo (pipeline->pipe_tgt2host_path, S_IRUSR | S_IWUSR) < 0)
    COIERROR ("Cannot create pipe %s.", pipeline->pipe_tgt2host_path);

  /* Send data to target.  */
  const cmd_t cmd = CMD_PIPELINE_CREATE;
  WRITE (proc->pipe_host2tgt, &cmd, sizeof (cmd_t));
  WRITE (proc->pipe_host2tgt, &pipeline_num, sizeof (pipeline_num));

  /* Create a new thread for the pipeline.  */
  if (pthread_create (&pipeline->thread, NULL, pipeline_thread_routine,
		      pipeline))
    COIERROR ("Cannot create new thread.");

  finish_critical_section ();

  /* Prepare output arguments.  */
  *out_pPipeline = (COIPIPELINE) pipeline;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIPipelineDestroy, 1) (COIPIPELINE in_Pipeline)
{
  COITRACE ("COIPipelineDestroy");

  assert (in_Pipeline != NULL);

  /* Convert input arguments.  */
  Pipeline *pipeline = (Pipeline *) in_Pipeline;

  start_critical_section ();
  /* Remove pipeline from the set of undestroyed pipelines.  */
  pipelines.erase (pipeline);

  /* Exit pipeline thread.  */
  pipeline->destroy = true;
  finish_critical_section ();

  while (!pipeline_is_destroyed (pipeline))
    usleep (1000);

  /* Join with a destroyed thread.  */
  if (pthread_join (pipeline->thread, NULL))
    COIERROR ("Cannot join with a thread.");

  delete pipeline;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIPipelineRunFunction, 1) (COIPIPELINE in_Pipeline,
					    COIFUNCTION in_Function,
					    uint32_t in_NumBuffers,
					    const COIBUFFER *in_Buffers,
					    const COI_ACCESS_FLAGS *in_pBufferAccessFlags, // Ignored
					    uint32_t in_NumDependencies,
					    const COIEVENT *in_pDependencies,		   // Ignored
					    const void *in_pMiscData,
					    uint16_t in_MiscDataLen,
					    void *out_pAsyncReturnValue,
					    uint16_t in_AsyncReturnValueLen,
					    COIEVENT *out_pCompletion)
{
  COITRACE ("COIPipelineRunFunction");

  /* Features of liboffloadmic.  */
  assert (in_Pipeline != NULL);
  assert (in_Function != NULL);
  assert (in_NumDependencies == 0);

  Function func;
  func.ptr = (void *) in_Function;
  func.num_buffers = in_NumBuffers;
  func.bufs_size = new uint64_t [in_NumBuffers];
  func.bufs_data_target = new void * [in_NumBuffers];
  for (uint32_t i = 0; i < in_NumBuffers; i++)
    {
      Buffer **bufs = (Buffer **) in_Buffers;
      func.bufs_size[i] = bufs[i]->size;
      func.bufs_data_target[i] = bufs[i]->data_target;
    }
  func.misc_data = (void *) in_pMiscData;
  func.misc_data_len = in_MiscDataLen;
  func.return_value = out_pAsyncReturnValue;
  func.return_value_len = in_AsyncReturnValueLen;

  start_critical_section ();
  func.completion_event.opaque[0] = max_event_num++;
  non_signalled_events.insert (func.completion_event.opaque[0]);
  ((Pipeline *) in_Pipeline)->queue.push (func);
  finish_critical_section ();

  /* In case of synchronous execution we have to wait for target.  */
  if (out_pCompletion == NULL)
    COIEventWait (1, &func.completion_event, -1, 1, NULL, NULL);
  else
    *out_pCompletion = func.completion_event;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIProcessCreateFromMemory, 1) (COIENGINE in_Engine,
						const char *in_pBinaryName,
						const void *in_pBinaryBuffer,
						uint64_t in_BinaryBufferLength,
						int in_Argc,
						const char **in_ppArgv,
						uint8_t in_DupEnv,
						const char **in_ppAdditionalEnv,
						uint8_t in_ProxyActive,		// Ignored
						const char *in_Reserved,	// Ignored
						uint64_t in_InitialBufferSpace,	// Ignored
						const char *in_LibrarySearchPath,
						const char *in_FileOfOrigin,    // Ignored
						uint64_t in_FileOfOriginOffset,	// Ignored
						COIPROCESS *out_pProcess)
{
  COITRACE ("COIProcessCreateFromMemory");

  const int run_max_args_num = 128;
  char *run_argv[run_max_args_num];
  char *emul_run = getenv (OFFLOAD_EMUL_RUN_ENV);
  const int uint_max_len = 11;

  /* Features of liboffloadmic.  */
  assert (in_Engine != NULL);
  assert (in_pBinaryName != NULL);
  assert (in_pBinaryBuffer != NULL);
  assert (in_Argc == 0);
  assert (in_ppArgv == NULL);
  assert (in_ppAdditionalEnv == NULL);
  assert (in_LibrarySearchPath != NULL);
  assert (out_pProcess != NULL);

  /* Convert input arguments.  */
  Engine *eng = (Engine *) in_Engine;

  /* Create temporary directory for engine files.  */
  assert (eng->dir == NULL);
  STRDUP (eng->dir, ENGINE_PATH);
  if (mkdtemp (eng->dir) == NULL)
    COIERROR ("Cannot create temporary directory %s.", eng->dir);

  /* Save path to engine directory for clean up on exit.  */
  tmp_dirs_num++;
  tmp_dirs = (char **) realloc (tmp_dirs, tmp_dirs_num * sizeof (char *));
  if (!tmp_dirs)
    COIERROR ("Cannot allocate memory.");
  STRDUP (tmp_dirs[tmp_dirs_num - 1], eng->dir);

  /* Create target executable file.  */
  char *target_exe;
  MALLOC (char *, target_exe, strlen (eng->dir) + strlen (in_pBinaryName) + 2);
  sprintf (target_exe, "%s/%s", eng->dir, in_pBinaryName);
  int fd = open (target_exe, O_CLOEXEC | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
  if (fd < 0)
    COIERROR ("Cannot create file %s.", target_exe);
  FILE *file = fdopen (fd, "wb");
  if (file == NULL)
    COIERROR ("Cannot associate stream with file descriptor.");
  if (fwrite (in_pBinaryBuffer, 1, in_BinaryBufferLength, file)
      != in_BinaryBufferLength)
    COIERROR ("Cannot write in file %s.", target_exe);
  if (fclose (file) != 0)
    COIERROR ("Cannot close file %s.", target_exe);

  /* Fix file permissions.  */
  if (chmod (target_exe, S_IRWXU) < 0)
    COIERROR ("Cannot change permissions for file %s.", target_exe);

  /* Create directory for pipes to prevent names collision.  */
  char *pipes_path;
  MALLOC (char *, pipes_path, strlen (eng->dir) + sizeof (PIPES_PATH));
  sprintf (pipes_path, "%s" PIPES_PATH, eng->dir);
  if (mkdir (pipes_path, S_IRWXU) < 0)
    COIERROR ("Cannot create folder %s.", pipes_path);

  /* Create 2 main pipes for inter-process communication.  */
  char *pipe_host2tgt_path, *pipe_tgt2host_path;
  MALLOC (char *, pipe_host2tgt_path,
	  strlen (eng->dir) + sizeof (PIPE_HOST2TGT_NAME "mainpipe"));
  MALLOC (char *, pipe_tgt2host_path,
	  strlen (eng->dir) + sizeof (PIPE_TGT2HOST_NAME "mainpipe"));
  sprintf (pipe_host2tgt_path, "%s" PIPE_HOST2TGT_NAME "mainpipe", eng->dir);
  sprintf (pipe_tgt2host_path, "%s" PIPE_TGT2HOST_NAME "mainpipe", eng->dir);
  if (mkfifo (pipe_host2tgt_path, S_IRUSR | S_IWUSR) < 0)
    COIERROR ("Cannot create main pipe %s.", pipe_host2tgt_path);
  if (mkfifo (pipe_tgt2host_path, S_IRUSR | S_IWUSR) < 0)
    COIERROR ("Cannot create main pipe %s.", pipe_tgt2host_path);

  /* Prepare argv.  */
  if (emul_run == NULL || strcmp (emul_run, "") == 0)
    {
      STRDUP (run_argv[0], target_exe);
      run_argv[1] = (char *) NULL;
    }
  else
    {
      char *ptr, *tmp;
      int i = 0;
      STRDUP (tmp, emul_run);
      char *tok = strtok_r (tmp, " ", &ptr);
      while (tok != NULL)
	{
	  if (i >= run_max_args_num)
	    COIERROR ("Run command has too many arguments.");
	  STRDUP (run_argv[i++], tok);
	  tok = strtok_r (NULL, " ", &ptr);
	}
      STRDUP (run_argv[i], target_exe);
      run_argv[i + 1] = (char *) NULL;
      free (tmp);
    }

  /* Prepare envp.  */
  int env_num = 0;
  if (in_DupEnv == true)
    while (environ[env_num++]);
  env_num += 4; // LD_LIBRARY_PATH, MIC_DIR, MIC_INDEX, NULL

  char **envp;
  MALLOC (char **, envp, env_num * sizeof (char *));

  int env_i = 0;
  if (in_DupEnv == true)
    for (unsigned i = 0; environ[i] != NULL; i++)
      {
	unsigned j;
	char *env_name;
	STRDUP (env_name, environ[i]);
	for (j = 0; env_name[j] != '=' && env_name[j] != '\0'; j++);
	env_name[j] = '\0';
	if (strcmp (env_name, "LD_LIBRARY_PATH") != 0
	    && strcmp (env_name, MIC_DIR_ENV) != 0
	    && strcmp (env_name, MIC_INDEX_ENV) != 0)
	  STRDUP (envp[env_i++], environ[i]);
	free (env_name);
      }

  MALLOC (char *, envp[env_i], strlen (MIC_DIR_ENV) + strlen (eng->dir) + 2);
  sprintf (envp[env_i], "%s=%s", MIC_DIR_ENV, eng->dir);

  MALLOC (char *, envp[env_i + 1], strlen (MIC_INDEX_ENV) + uint_max_len + 1);
  sprintf (envp[env_i + 1], "%s=%u", MIC_INDEX_ENV, eng->index);

  MALLOC (char *, envp[env_i + 2],
	  strlen ("LD_LIBRARY_PATH=") + strlen (in_LibrarySearchPath) + 1);
  sprintf (envp[env_i + 2], "LD_LIBRARY_PATH=%s", in_LibrarySearchPath);

  envp[env_i + 3] = (char *) NULL;

  /* Create target process.  */
  pid_t pid = vfork ();
  if (pid < 0)
    COIERROR ("Cannot create child process.");

  if (pid == 0)
    {
      /* Run target executable.  */
      if (execvpe (run_argv[0], run_argv, envp) == -1)
	COIERROR ("Cannot execute file %s.", target_exe);
    }

  /* Open main pipes.  */
  int pipe_host2tgt = open (pipe_host2tgt_path, O_CLOEXEC | O_WRONLY);
  if (pipe_host2tgt < 0)
    COIERROR ("Cannot open host-to-target main pipe.");
  int pipe_tgt2host = open (pipe_tgt2host_path, O_CLOEXEC | O_RDONLY);
  if (pipe_tgt2host < 0)
    COIERROR ("Cannot open target-to-host main pipe.");

  /* Create process handle.  */
  Process *proc = new Process;
  proc->pid = pid;
  proc->pipe_host2tgt = pipe_host2tgt;
  proc->pipe_tgt2host = pipe_tgt2host;
  proc->engine = eng;
  proc->functions = NULL;

  /* Prepare output arguments.  */
  *out_pProcess = (COIPROCESS) proc;

  /* Clean up.  */
  for (unsigned i = 0; run_argv[i] != NULL; i++)
    free (run_argv[i]);
  for (unsigned i = 0; envp[i] != NULL; i++)
    free (envp[i]);
  free (envp);
  free (pipe_host2tgt_path);
  free (pipe_tgt2host_path);
  free (pipes_path);
  free (target_exe);

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIProcessCreateFromFile, 1) (COIENGINE in_Engine,
					      const char *in_pBinaryName,
					      int in_Argc,
					      const char **in_ppArgv,
					      uint8_t in_DupEnv,
					      const char **in_ppAdditionalEnv,
					      uint8_t in_ProxyActive,
					      const char *in_Reserved,
					      uint64_t in_BufferSpace,
					      const char *in_LibrarySearchPath,
					      COIPROCESS *out_pProcess)
{
  COITRACE ("COIProcessCreateFromFile");

  /* liboffloadmic with GCC compiled binaries should never go here.  */
  assert (false);
  return COI_ERROR;
}


COIRESULT
SYMBOL_VERSION (COIProcessDestroy, 1) (COIPROCESS in_Process,
				       int32_t in_WaitForMainTimeout, // Ignored
				       uint8_t in_ForceDestroy,
				       int8_t *out_pProcessReturn,
				       uint32_t *out_pTerminationCode)
{
  COITRACE ("COIProcessDestroy");

  assert (in_Process != NULL);
  assert (out_pProcessReturn != NULL);
  assert (out_pTerminationCode != NULL);

  /* Convert input arguments.  */
  Process *proc = (Process *) in_Process;

  /* Destroy all undestroyed pipelines.  */
  while (!pipelines.empty ())
    {
      std::set<Pipeline *>::iterator p = pipelines.begin ();
      COIPipelineDestroy ((COIPIPELINE) *p);
    }

  /* Close main pipes.  */
  if (close (proc->pipe_host2tgt) < 0)
    COIERROR ("Cannot close host-to-target main pipe.");
  if (close (proc->pipe_tgt2host) < 0)
    COIERROR ("Cannot close target-to-host main pipe.");

  /* Shutdown target process by force.  */
  if (in_ForceDestroy)
    kill (proc->pid, SIGTERM);

  /* Clean up.  */
  free (proc->engine->dir);
  free (proc->functions);
  delete proc->engine;
  delete proc;

  /* Prepare output arguments.  */
  *out_pProcessReturn = 0;
  *out_pTerminationCode = 0;

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIProcessGetFunctionHandles, 1) (COIPROCESS in_Process,
						  uint32_t in_NumFunctions,
						  const char **in_ppFunctionNameArray,
						  COIFUNCTION *out_pFunctionHandleArray)
{
  COITRACE ("COIProcessGetFunctionHandles");

  assert (in_Process != NULL);
  assert (in_ppFunctionNameArray != NULL);
  assert (out_pFunctionHandleArray != NULL);

  /* Convert input arguments.  */
  Process *proc = (Process *) in_Process;

  /* This function should be called once for the process.  */
  assert (proc->functions == NULL);

  /* Create array of function pointers.  Last element is 0, what shows the end
     of the array.  This array is used to free memory when process is
     destroyed.  */
  proc->functions = (void **) calloc (in_NumFunctions + 1, sizeof (void *));
  if (proc->functions == NULL)
    COIERROR ("Cannot allocate memory.");

  /* Get handles for functions.  */
  for (uint32_t i = 0; i < in_NumFunctions; i++)
    {
      size_t len = strlen (in_ppFunctionNameArray[i]) + 1;

      start_critical_section ();

      /* Send data to target.  */
      const cmd_t cmd = CMD_GET_FUNCTION_HANDLE;
      WRITE (proc->pipe_host2tgt, &cmd, sizeof (cmd_t));
      WRITE (proc->pipe_host2tgt, &len, sizeof (size_t));
      WRITE (proc->pipe_host2tgt, in_ppFunctionNameArray[i], len);

      /* Receive data from target.  */
      void *fn_ptr;
      READ (proc->pipe_tgt2host, &fn_ptr, sizeof (void *));

      finish_critical_section ();

      /* Save function pointer.  */
      proc->functions[i] = fn_ptr;

      /* Prepare output arguments.  */
      out_pFunctionHandleArray[i] = (COIFUNCTION) fn_ptr;
    }

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIProcessLoadLibraryFromMemory, 2) (COIPROCESS in_Process,
						     const void *in_pLibraryBuffer,
						     uint64_t in_LibraryBufferLength,
						     const char *in_pLibraryName,
						     const char *in_LibrarySearchPath,	// Ignored
						     const char *in_FileOfOrigin,	// Ignored
						     uint64_t in_FileOfOriginOffset,	// Ignored
						     uint32_t in_Flags,			// Ignored
						     COILIBRARY *out_pLibrary)
{
  COITRACE ("COIProcessLoadLibraryFromMemory");

  assert (in_Process != NULL);
  assert (in_pLibraryBuffer != NULL);
  assert (out_pLibrary != NULL);

  /* Convert input arguments.  */
  Process *proc = (Process *) in_Process;

  /* Create target library file.  */
  char *lib_path;
  size_t len = strlen (proc->engine->dir) + strlen (in_pLibraryName) + 2;
  MALLOC (char *, lib_path, len);
  sprintf (lib_path, "%s/%s", proc->engine->dir, in_pLibraryName);
  int fd = open (lib_path, O_CLOEXEC | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
  if (fd < 0)
    COIERROR ("Cannot create file %s.", lib_path);
  FILE *file = fdopen (fd, "wb");
  if (file == NULL)
    COIERROR ("Cannot associate stream with file descriptor.");
  if (fwrite (in_pLibraryBuffer, 1, in_LibraryBufferLength, file)
      != in_LibraryBufferLength)
    COIERROR ("Cannot write in file %s.", lib_path);
  if (fclose (file) != 0)
    COIERROR ("Cannot close file %s.", lib_path);

  start_critical_section ();

  /* Make target open library.  */
  const cmd_t cmd = CMD_OPEN_LIBRARY;
  WRITE (proc->pipe_host2tgt, &cmd, sizeof (cmd_t));
  WRITE (proc->pipe_host2tgt, &len, sizeof (size_t));
  WRITE (proc->pipe_host2tgt, lib_path, len);

  /* Receive data from target.  */
  void *handle;
  READ (proc->pipe_tgt2host, &handle, sizeof (void *));

  finish_critical_section ();

  /* Clean up.  */
  free (lib_path);

  *out_pLibrary = (COILIBRARY) handle;
  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIProcessRegisterLibraries, 1) (uint32_t in_NumLibraries,		      // Ignored
						 const void **in_ppLibraryArray,	      // Ignored
						 const uint64_t *in_pLibrarySizeArray,	      // Ignored
						 const char **in_ppFileOfOriginArray,	      // Ignored
						 const uint64_t *in_pFileOfOriginOffSetArray) // Ignored
{
  COITRACE ("COIProcessRegisterLibraries");

  /* Looks like we have nothing to do here.  */

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIProcessUnloadLibrary, 1) (COIPROCESS in_Process,
					     COILIBRARY in_Library)
{
  COITRACE ("COIProcessUnloadLibrary");

  assert (in_Process != NULL);
  assert (in_Library != NULL);

  const cmd_t cmd = CMD_CLOSE_LIBRARY;

  /* Convert input arguments.  */
  Process *proc = (Process *) in_Process;

  start_critical_section ();

  /* Make target close library.  */
  WRITE (proc->pipe_host2tgt, &cmd, sizeof (cmd_t));
  WRITE (proc->pipe_host2tgt, &in_Library, sizeof (void *));

  finish_critical_section ();

  return COI_SUCCESS;
}


uint64_t
SYMBOL_VERSION (COIPerfGetCycleFrequency, 1) ()
{
  COITRACE ("COIPerfGetCycleFrequency");

  return (uint64_t) CYCLE_FREQUENCY;
}


COIRESULT
SYMBOL_VERSION (COIPipelineClearCPUMask, 1) (COI_CPU_MASK *in_Mask)
{
  COITRACE ("COIPipelineClearCPUMask");

  /* Looks like we have nothing to do here.  */

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIPipelineSetCPUMask, 1) (COIPROCESS in_Process,
					   uint32_t in_CoreID,
					   uint8_t in_ThreadID,
					   COI_CPU_MASK *out_pMask)
{
  COITRACE ("COIPipelineSetCPUMask");

  /* Looks like we have nothing to do here.  */

  return COI_SUCCESS;
}


COIRESULT
SYMBOL_VERSION (COIEngineGetInfo, 1) (COIENGINE in_EngineHandle,  // Ignored
				      uint32_t in_EngineInfoSize, // Ignored
				      COI_ENGINE_INFO *out_pEngineInfo)
{
  COITRACE ("COIEngineGetInfo");

  assert (out_pEngineInfo != NULL);

  out_pEngineInfo->ISA = COI_ISA_x86_64;
  out_pEngineInfo->NumCores = 1;
  out_pEngineInfo->NumThreads = 8;
  out_pEngineInfo->CoreMaxFrequency = SYMBOL_VERSION(COIPerfGetCycleFrequency,1)() / 1000000;
  out_pEngineInfo->PhysicalMemory = 1024;
  out_pEngineInfo->PhysicalMemoryFree = 1024;
  out_pEngineInfo->SwapMemory = 1024;
  out_pEngineInfo->SwapMemoryFree = 1024;
  out_pEngineInfo->MiscFlags = COI_ENG_ECC_DISABLED;

  return COI_SUCCESS;
}

} // extern "C"

