/* Shared memory-multiple (process)-image implementation of GNU Fortran
   Coarray Library
   Copyright (C) 2011-2026 Free Software Foundation, Inc.
   Based on single.c contributed by Tobias Burnus <burnus@net-b.de>

This file is part of the GNU Fortran Coarray Runtime Library (libcaf).

Libcaf is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

Libcaf is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#include "libcaf.h"
#include "caf_error.h"

#include "shmem/counter_barrier.h"
#include "shmem/supervisor.h"
#include "shmem/teams_mgmt.h"
#include "shmem/thread_support.h"

#include <stdlib.h> /* For exit and malloc.  */
#include <string.h> /* For memcpy and memset.  */
#include <stdint.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>

/* Define GFC_CAF_CHECK to enable run-time checking.  */
/* #define GFC_CAF_CHECK  1  */

#define TOKEN(X) ((caf_shmem_token_t) (X))
#define MEMTOK(X) ((caf_shmem_token_t) (X))->memptr

/* Global variables.  */
static caf_static_t *caf_static_list = NULL;
memid next_memid = 0;

typedef void (*getter_t) (void *, const int *, void **, int32_t *, void *,
			  caf_token_t, const size_t, size_t *, const size_t *);
typedef void (*is_present_t) (void *, const int *, int32_t *, void *,
			      caf_shmem_token_t, const size_t);
typedef void (*receiver_t) (void *, const int *, void *, const void *,
			    caf_token_t, const size_t, const size_t *,
			    const size_t *);
struct accessor_hash_t
{
  int hash;
  int pad;
  union
  {
    getter_t getter;
    is_present_t is_present;
    receiver_t receiver;
  } u;
};

static struct accessor_hash_t *accessor_hash_table = NULL;
static int aht_cap = 0;
static int aht_size = 0;
static enum {
  AHT_UNINITIALIZED,
  AHT_OPEN,
  AHT_PREPARED
} accessor_hash_table_state
  = AHT_UNINITIALIZED;

void
_gfortran_caf_init (int *argc, char ***argv)
{
  int exit_code = 0;

  ensure_shmem_initialization ();

  if (shared_memory_get_env ())
    {
      /* This is the initialization of a worker.  */
      _gfortran_caf_sync_all (NULL, NULL, 0);
      return;
    }

  if (supervisor_main_loop (argc, argv, &exit_code))
    return;

  thread_support_cleanup ();
  shared_memory_cleanup (&local->sm);

  /* Free pseudo tokens and memory to allow main process to survive caf_init.
   */
  while (caf_static_list != NULL)
    {
      caf_static_t *tmp = caf_static_list->prev;
      free (((caf_shmem_token_t) caf_static_list->token)->base);
      free (caf_static_list->token);
      free (caf_static_list);
      caf_static_list = tmp;
    }
  free (local);

  exit (exit_code);
}

static void
free_team_list (caf_shmem_team_t l)
{
  while (l != NULL)
    {
      caf_shmem_team_t p = l->parent;
      struct coarray_allocated *ca = l->allocated;
      while (ca)
	{
	  struct coarray_allocated *nca = ca->next;
	  free (ca);
	  ca = nca;
	}
      free (l);
      l = p;
    }
}

void
_gfortran_caf_finalize (void)
{
  free (accessor_hash_table);

  while (caf_static_list != NULL)
    {
      caf_static_t *tmp = caf_static_list->prev;
      alloc_free_memory_with_id (
	&local->ai,
	(memid) ((caf_shmem_token_t) caf_static_list->token)->token_id);
      free (caf_static_list->token);
      free (caf_static_list);
      caf_static_list = tmp;
    }

  /* Make sure to wait for all images to finish.  */
  sync_team (caf_initial_team);
  free_team_list (caf_current_team);
  caf_initial_team = caf_current_team = NULL;
  free_team_list (caf_teams_formed);
  caf_teams_formed = NULL;

  shared_memory_cleanup (&local->sm);
  free (local);

  thread_support_cleanup ();
}

int
_gfortran_caf_this_image (caf_team_t team)
{
  return (team ? ((caf_shmem_team_t) team)->index : caf_current_team->index)
	 + 1;
}

int
_gfortran_caf_num_images (caf_team_t team, int32_t *team_number)
{
#define CHECK_TEAMS                                                            \
  while (cur)                                                                  \
    {                                                                          \
      if (cur->u.image_info->team_id == *team_number)                          \
	return counter_barrier_get_count (&cur->u.image_info->image_count);    \
      cur = cur->parent;                                                       \
    }

  if (team)
    return counter_barrier_get_count (
      &((caf_shmem_team_t) team)->u.image_info->image_count);

  if (team_number)
    {
      caf_shmem_team_t cur = caf_current_team;

      CHECK_TEAMS

      cur = caf_teams_formed;
      CHECK_TEAMS
    }

  return counter_barrier_get_count (
    &caf_current_team->u.image_info->image_count);
}


void
_gfortran_caf_register (size_t size, caf_register_t type, caf_token_t *token,
			gfc_descriptor_t *data, int *stat, char *errmsg,
			size_t errmsg_len)
{
  static bool inited = false;
  const char alloc_fail_msg[] = "Failed to allocate coarray";
  void *mem;
  caf_shmem_token_t shmem_token;

  /* When the master has not been initialized, we could either be in the
     control process or in the static initializer phase.  */
  if (unlikely (!inited))
    {
      if (local == NULL)
	{
	  if (shared_memory_get_env ())
	    {
	      /* This is the static initializer phase.  Register the static
		 coarrays or we are in trouble later.  */
	      ensure_shmem_initialization ();
	      inited = true;
	    }
	  else if (type == CAF_REGTYPE_COARRAY_STATIC)
	    {
	      /* This is the control process, but it also runs the static
		 initializers (the caf_init.N() procedures).  In these it may
		 want to assign to members (effectively NULL them) of derived
		 types.  Therefore the need to return valid memory blocks.
		 These are never used and do not participate in any coarray
		 routine.  They unfortunately just waste some memory.  */
	      mem = malloc (size);
	      GFC_DESCRIPTOR_DATA (data) = mem;
	      caf_static_t *tmp = malloc (sizeof (caf_static_t));
	      *token = malloc (sizeof (struct caf_shmem_token));
	      **(caf_shmem_token_t *) token
		= (struct caf_shmem_token) {mem, NULL, mem, size, ~0U, true};
	      *tmp = (caf_static_t) {*token, caf_static_list};
	      caf_static_list = tmp;
	      return;
	    }
	  else
	    return;
	}
    }

  /* Catch all special cases.  */
  switch (type)
    {
    /* When mapping, read from the old token.  */
    case CAF_REGTYPE_COARRAY_MAP_EXISTING:
      /* The mapping could involve an offset that is mangled into the array's
	 data ptr.  */
      mem
	= ((caf_shmem_token_t) *token)->base
	  + (GFC_DESCRIPTOR_DATA (data) - ((caf_shmem_token_t) *token)->memptr);
      size = ((caf_shmem_token_t) *token)->image_size;
      break;
    case CAF_REGTYPE_EVENT_ALLOC:
    case CAF_REGTYPE_EVENT_STATIC:
      size *= sizeof (void *);
      break;
    default:
      break;
    }

  if (type != CAF_REGTYPE_COARRAY_ALLOC_ALLOCATE_ONLY)
    *token = malloc (sizeof (struct caf_shmem_token));

  size = alignto (size, sizeof (ptrdiff_t));
  switch (type)
    {
    case CAF_REGTYPE_LOCK_STATIC:
    case CAF_REGTYPE_LOCK_ALLOC:
    case CAF_REGTYPE_CRITICAL:
      {
	lock_t *addr;
	bool created;
	size_t alloc_size;

	allocator_lock (&local->ai.alloc);
#if defined(WIN32) || defined(__CYGWIN__)
	/* On Windows mutexes are not an object stored in the shmem but
	   identified by an id.  */
	alloc_size = size * caf_current_team->u.image_info->image_count.count;
#else
	alloc_size = size;
#endif
	addr = alloc_get_memory_by_id_created (&local->ai,
					       alloc_size * sizeof (lock_t),
					       next_memid, &created);

	if (created)
	  {
	    /* Initialize the mutex only, when the memory was allocated for the
	       first time.  */
	    for (size_t c = 0; c < alloc_size; ++c)
	      initialize_shared_errorcheck_mutex (&addr[c]);
	  }
	size *= sizeof (lock_t);

	allocator_unlock (&local->ai.alloc);
	mem = addr;
	break;
      }
    case CAF_REGTYPE_EVENT_STATIC:
    case CAF_REGTYPE_EVENT_ALLOC:
      {
	bool created;

	allocator_lock (&local->ai.alloc);
	mem = alloc_get_memory_by_id_created (
	  &local->ai, size * caf_current_team->u.image_info->image_count.count,
	  next_memid, &created);
	if (created)
	  memset (mem, 0,
		  size * caf_current_team->u.image_info->image_count.count);
	allocator_unlock (&local->ai.alloc);
      }
      break;
    case CAF_REGTYPE_COARRAY_ALLOC_REGISTER_ONLY:
      mem = NULL;
      break;
    case CAF_REGTYPE_COARRAY_ALLOC_ALLOCATE_ONLY:
      allocator_lock (&local->ai.alloc);
      mem = SHMPTR_AS (void *, allocator_shared_malloc (&local->ai.alloc, size),
		       &local->sm);
      allocator_unlock (&local->ai.alloc);
      break;
    case CAF_REGTYPE_COARRAY_MAP_EXISTING:
      /* Computing the mem ptr is done above before the new token is allocated.
       */
      break;
    default:
      mem = alloc_get_memory_by_id (
	&local->ai, size * caf_current_team->u.image_info->image_count.count,
	next_memid);
      break;
    }

  if (unlikely (
	*token == NULL
	|| (mem == NULL && type != CAF_REGTYPE_COARRAY_ALLOC_REGISTER_ONLY)))
    {
      /* Freeing the memory conditionally seems pointless, but
	 caf_internal_error () may return, when a stat is given and then the
	 memory may be lost.  */
      if (mem)
	alloc_free_memory_with_id (&local->ai, next_memid);
      free (*token);
      caf_internal_error (alloc_fail_msg, stat, errmsg, errmsg_len);
      return;
    }

  shmem_token = TOKEN (*token);
  switch (type)
    {
    case CAF_REGTYPE_COARRAY_ALLOC_REGISTER_ONLY:
      *shmem_token
	= (struct caf_shmem_token) {NULL, NULL, NULL, size, ~0U, false};
      break;
    case CAF_REGTYPE_COARRAY_ALLOC_ALLOCATE_ONLY:
      shmem_token->memptr = mem;
      shmem_token->base = mem;
      shmem_token->image_size = size;
      shmem_token->owning_memory = true;
      break;
    case CAF_REGTYPE_COARRAY_MAP_EXISTING:
      *shmem_token
	= (struct caf_shmem_token) {mem + size * this_image.image_num,
				    GFC_DESCRIPTOR_RANK (data) > 0 ? data
								   : NULL,
				    mem,
				    size,
				    next_memid++,
				    false};
      break;
    case CAF_REGTYPE_LOCK_STATIC:
    case CAF_REGTYPE_LOCK_ALLOC:
    case CAF_REGTYPE_CRITICAL:
      *shmem_token = (struct caf_shmem_token) {
	mem,	      GFC_DESCRIPTOR_RANK (data) > 0 ? data : NULL,
	mem,	      size,
	next_memid++, false};
      break;
    default:
      *shmem_token
	= (struct caf_shmem_token) {mem + size * this_image.image_num,
				    GFC_DESCRIPTOR_RANK (data) > 0 ? data
								   : NULL,
				    mem,
				    size,
				    next_memid++,
				    true};
      break;
    }

  if (stat)
    *stat = 0;

  if (type == CAF_REGTYPE_COARRAY_STATIC || type == CAF_REGTYPE_LOCK_STATIC
      || type == CAF_REGTYPE_CRITICAL || type == CAF_REGTYPE_EVENT_STATIC)
    {
      caf_static_t *tmp = malloc (sizeof (caf_static_t));
      *tmp = (caf_static_t) {*token, caf_static_list};
      caf_static_list = tmp;
    }
  else
    {
      struct coarray_allocated *ca = caf_current_team->allocated;
      for (; ca && ca->token != shmem_token; ca = ca->next)
	;
      if (!ca)
	{
	  ca = (struct coarray_allocated *) malloc (
	    sizeof (struct coarray_allocated));
	  *ca = (struct coarray_allocated) {caf_current_team->allocated,
					    shmem_token};
	  caf_current_team->allocated = ca;
	}
    }
  GFC_DESCRIPTOR_DATA (data) = shmem_token->memptr;
}

void
_gfortran_caf_deregister (caf_token_t *token, caf_deregister_t type, int *stat,
			  char *errmsg __attribute__ ((unused)),
			  size_t errmsg_len __attribute__ ((unused)))
{
  caf_shmem_token_t shmem_token = TOKEN (*token);

  if (shmem_token->owning_memory && shmem_token->memptr)
    {
      if (shmem_token->token_id != ~0U)
	alloc_free_memory_with_id (&local->ai, (memid) shmem_token->token_id);
      else
	{
	  allocator_lock (&local->ai.alloc);
	  allocator_shared_free (&local->ai.alloc,
				 AS_SHMPTR (shmem_token->base, local->sm),
				 shmem_token->image_size);
	  allocator_unlock (&local->ai.alloc);
	}

      if (shmem_token->desc)
	GFC_DESCRIPTOR_DATA (shmem_token->desc) = NULL;
    }

  if (type != CAF_DEREGTYPE_COARRAY_DEALLOCATE_ONLY)
    {
      struct coarray_allocated *ca = caf_current_team->allocated;
      if (ca && caf_current_team->allocated->token == shmem_token)
	caf_current_team->allocated = ca->next;
      else
	{
	  struct coarray_allocated *pca = NULL;
	  for (; ca && ca->token != shmem_token; pca = ca, ca = ca->next)
	    ;
	  if (!ca)
	    caf_runtime_error (
	      "Coarray token to be freeed is not in current team %d", type);
	  /* Unhook found coarray_allocated node from list...  */
	  pca->next = ca->next;
	}
      /* ... and free.  */
      free (ca);
      free (TOKEN (*token));
      *token = NULL;
      sync_team (caf_current_team);
    }
  else
    {
      shmem_token->memptr = NULL;
      shmem_token->owning_memory = false;
    }

  if (stat)
    *stat = 0;
}

void
_gfortran_caf_sync_all (int *stat, char *errmsg, size_t errmsg_len)
{
  __asm__ __volatile__ ("":::"memory");
  HEALTH_CHECK (stat, errmsg, errmsg_len);
  CHECK_TEAM_INTEGRITY (caf_current_team);
  sync_all ();
}


void
_gfortran_caf_sync_memory (int *stat,
			   char *errmsg __attribute__ ((unused)),
			   size_t errmsg_len __attribute__ ((unused)))
{
  __asm__ __volatile__ ("":::"memory");
  if (stat)
    *stat = 0;
}

void
_gfortran_caf_sync_images (int count, int images[], int *stat, char *errmsg,
			   size_t errmsg_len)
{
  int *mapped_images = images;

  CHECK_TEAM_INTEGRITY (caf_current_team);
  if (count > 0)
    {
      int *map = caf_current_team->u.image_info->image_map;
      int max_id = caf_current_team->u.image_info->image_map_size;

      mapped_images = __builtin_alloca (sizeof (int) * count);
      if (!mapped_images)
	{
	  caf_internal_error ("SYNC IMAGES: Can not reserve buffer for mapping "
			      "images to internal ids. Increase stack size!",
			      stat, errmsg, errmsg_len);
	  return;
	}
      for (int c = 0; c < count; ++c)
	{
	  if (images[c] > 0 && images[c] <= max_id)
	    {
	      mapped_images[c] = map[images[c] - 1];
	      switch (this_image.supervisor->images[mapped_images[c]].status)
		{
		case IMAGE_SUCCESS:
		  caf_internal_error ("SYNC IMAGES: Image %d is stopped", stat,
				      errmsg, errmsg_len, images[c]);
		  /* We can come here only, when stat is non-NULL.  */
		  *stat = CAF_STAT_STOPPED_IMAGE;
		  return;
		case IMAGE_FAILED:
		  caf_internal_error ("SYNC IMAGES: Image %d has failed", stat,
				      errmsg, errmsg_len, images[c]);
		  /* We can come here only, when stat is non-NULL.  */
		  *stat = CAF_STAT_FAILED_IMAGE;
		  return;
		default:
		  break;
		}
	      for (int i = 0; i < c; ++i)
		if (mapped_images[c] == mapped_images[i])
		  {
		    caf_internal_error ("SYNC IMAGES: Duplicate image %d in "
					"images at position %d and &d.",
					stat, errmsg, errmsg_len, images[c],
					i + 1, c + 1);
		    /* There is no official error code for this, but 3 is what
		       OpenCoarray uses.  */
		    *stat = 3;
		    return;
		  }
	    }
	  else
	    {
	      caf_internal_error ("Invalid image number %d in SYNC IMAGES",
				  stat, errmsg, errmsg_len, images[c]);
	      return;
	    }
	}
    }
  else
    HEALTH_CHECK (stat, errmsg, errmsg_len);

  __asm__ __volatile__ ("" ::: "memory");
  sync_table (&local->si, mapped_images, count);
  HEALTH_CHECK (stat, errmsg, errmsg_len);
}

extern void _gfortran_report_exception (void);

void
_gfortran_caf_stop_numeric (int stop_code, bool quiet)
{
  if (!quiet)
    {
      _gfortran_report_exception ();
      fprintf (stderr, "STOP %d\n", stop_code);
    }
  exit (stop_code);
}

void
_gfortran_caf_stop_str (const char *string, size_t len, bool quiet)
{
  if (!quiet)
    {
      _gfortran_report_exception ();
      fputs ("STOP ", stderr);
      while (len--)
	fputc (*(string++), stderr);
      fputs ("\n", stderr);
    }
  exit (0);
}


void
_gfortran_caf_error_stop_str (const char *string, size_t len, bool quiet)
{
  if (!quiet)
    {
      _gfortran_report_exception ();
      fputs ("ERROR STOP ", stderr);
      while (len--)
	fputc (*(string++), stderr);
      fputs ("\n", stderr);
    }
  exit (1);
}

/* Report that the program terminated because of a fail image issued.  */

void
_gfortran_caf_fail_image (void)
{
  fputs ("IMAGE FAILED!\n", stderr);
  this_image.supervisor->images[this_image.image_num].status = IMAGE_FAILED;
  atomic_fetch_add (&this_image.supervisor->failed_images, 1);
  exit (0);
}

/* Get the status of image IMAGE.  */

int
_gfortran_caf_image_status (int image, caf_team_t *team)
{
  caf_shmem_team_t t = caf_current_team;
  int image_index;

  if (team)
    t = *(caf_shmem_team_t *) team;

  if (image > t->u.image_info->image_count.count)
    return CAF_STAT_STOPPED_IMAGE;

  image_index = t->u.image_info->image_map[image - 1];

  switch (this_image.supervisor->images[image_index].status)
    {
    case IMAGE_FAILED:
      return CAF_STAT_FAILED_IMAGE;
    case IMAGE_SUCCESS:
      return CAF_STAT_STOPPED_IMAGE;

    /* When image status is not known, return 0.  */
    case IMAGE_OK:
    case IMAGE_UNKNOWN:
    default:
      return 0;
    }
}

static void
stopped_or_failed_images (gfc_descriptor_t *array, caf_team_t *team, int *kind,
			  image_status img_stat, const char *function_name)
{
  int local_kind = kind != NULL ? *kind : 4;
  size_t sti = 0;
  caf_shmem_team_t t = caf_current_team;

  if (team)
    t = *(caf_shmem_team_t *) team;

  int sz = t->u.image_info->image_map_size;
  for (int i = 0; i < sz; ++i)
    if (this_image.supervisor->images[t->u.image_info->image_map[i]].status
	== img_stat)
      ++sti;

  if (sti)
    {
      array->base_addr = malloc (local_kind * sti);
      array->dtype.type = BT_INTEGER;
      array->dtype.elem_len = local_kind;
      array->dim[0].lower_bound = 1;
      array->dim[0]._ubound = sti;
      array->dim[0]._stride = 1;
      array->span = local_kind;
      array->offset = 0;
      sti = 0;
      for (int i = 0; i < sz; ++i)
	if (this_image.supervisor->images[t->u.image_info->image_map[i]].status
	    == img_stat)
	  switch (local_kind)
	    {
	    case 1:
	      ((int8_t *) array->base_addr)[sti++] = i + 1;
	      break;
	    case 2:
	      ((int16_t *) array->base_addr)[sti++] = i + 1;
	      break;
	    case 4:
	      ((int32_t *) array->base_addr)[sti++] = i + 1;
	      break;
	    case 8:
	      ((int64_t *) array->base_addr)[sti++] = i + 1;
	      break;
	    default:
	      caf_runtime_error ("Unsupported kind %d in %s.", local_kind,
				 function_name);
	    }
    }
  else
    {
      array->base_addr = NULL;
      array->dtype.type = BT_INTEGER;
      array->dtype.elem_len = local_kind;
      /* Setting lower_bound higher then upper_bound is what the compiler does
	 to indicate an empty array.  */
      array->dim[0].lower_bound = 0;
      array->dim[0]._ubound = -1;
      array->dim[0]._stride = 1;
      array->offset = 0;
    }
}

void
_gfortran_caf_failed_images (gfc_descriptor_t *array, caf_team_t *team,
			     int *kind)
{
  stopped_or_failed_images (array, team, kind, IMAGE_FAILED, "FAILED_IMAGES()");
}

void
_gfortran_caf_stopped_images (gfc_descriptor_t *array, caf_team_t *team,
			      int *kind)
{
  stopped_or_failed_images (array, team, kind, IMAGE_SUCCESS,
			    "STOPPED_IMAGES()");
}

void
_gfortran_caf_error_stop (int error, bool quiet)
{
  if (!quiet)
    {
      _gfortran_report_exception ();
      fprintf (stderr, "ERROR STOP %d\n", error);
    }
  exit (error);
}

static bool
check_get_team (caf_team_t *team, int *team_number, int *stat,
		caf_shmem_team_t *cur_team)
{
  if (team || team_number)
    {
      *cur_team = caf_current_team;

      if (team)
	{
	  caf_shmem_team_t cand_team = (caf_shmem_team_t) (*team);
	  while (*cur_team && *cur_team != cand_team)
	    *cur_team = (*cur_team)->parent;
	}
      else
	while (*cur_team && (*cur_team)->u.image_info->team_id != *team_number)
	  *cur_team = (*cur_team)->parent;

      if (!*cur_team)
	{
	  if (stat)
	    {
	      *stat = 1;
	      return false;
	    }
	  else
	    caf_runtime_error ("requested team not found");
	}
    }
  else
    *cur_team = caf_current_team;

  CHECK_TEAM_INTEGRITY ((*cur_team));
  return true;
}

static bool
check_map_team (int *remote_index, int *this_index, const int image_index,
		caf_team_t *team, int *team_number, int *stat)
{
  caf_shmem_team_t selected_team;
  const bool check = check_get_team (team, team_number, stat, &selected_team);

  if (!selected_team)
    return false;
#ifndef NDEBUG
  if (image_index < 1
      || image_index > selected_team->u.image_info->image_map_size)
    {
      if (stat)
	*stat = 1;
      return false;
    }
#endif

  *remote_index = selected_team->u.image_info->image_map[image_index - 1];

  *this_index = this_image.image_num;

  return check;
}

void
_gfortran_caf_co_broadcast (gfc_descriptor_t *desc, int source_image, int *stat,
			    char *errmsg __attribute__ ((unused)),
			    size_t errmsg_len __attribute__ ((unused)))
{
  int mapped_index, this_image_index;
  if (stat)
    *stat = 0;

  if (!check_map_team (&mapped_index, &this_image_index, source_image, NULL,
		       NULL, stat))
    return;

  collsub_broadcast_array (desc, mapped_index);
}

#define GEN_OP(name, op, type)                                                 \
  static type name##_##type (type *lhs, type *rhs) { return op (*lhs, *rhs); }

#define GEN_OP_SERIES(name, op)                                                \
  GEN_OP (name, op, uint8_t)                                                   \
  GEN_OP (name, op, uint16_t)                                                  \
  GEN_OP (name, op, uint32_t)                                                  \
  GEN_OP (name, op, uint64_t)                                                  \
  GEN_OP (name, op, int8_t)                                                    \
  GEN_OP (name, op, int16_t)                                                   \
  GEN_OP (name, op, int32_t)                                                   \
  GEN_OP (name, op, int64_t)                                                   \
  GEN_OP (name, op, float)                                                     \
  GEN_OP (name, op, double)

#define CO_ADD(l, r) ((l) + (r))
#define CO_MIN(l, r) ((l) < (r) ? (l) : (r))
#define CO_MAX(l, r) ((l) > (r) ? (l) : (r))
GEN_OP_SERIES (sum, CO_ADD)
GEN_OP_SERIES (min, CO_MIN)
GEN_OP_SERIES (max, CO_MAX)

// typedef void *(*opr_t) (void *, void *);
typedef void *opr_t;

#define GFC_DESCRIPTOR_KIND(desc) ((desc)->dtype.elem_len)

#define CASE_TYPE_KIND(name, type, ctype)                                      \
  case type:                                                                   \
    {                                                                          \
      switch (GFC_DESCRIPTOR_KIND (desc))                                      \
	{                                                                      \
	case 1:                                                                \
	  opr = (opr_t) name##_##ctype##8_t;                                   \
	  break;                                                               \
	case 2:                                                                \
	  opr = (opr_t) name##_##ctype##16_t;                                  \
	  break;                                                               \
	case 4:                                                                \
	  opr = (opr_t) name##_##ctype##32_t;                                  \
	  break;                                                               \
	case 8:                                                                \
	  opr = (opr_t) name##_##ctype##64_t;                                  \
	  break;                                                               \
	default:                                                               \
	  caf_runtime_error ("" #name                                          \
			     " not available for type/kind combination");      \
	  opr = NULL; /* Prevent false warnings.  */                           \
	}                                                                      \
      break;                                                                   \
    }

#define SWITCH_TYPE_KIND(name)                                                 \
  switch (GFC_DESCRIPTOR_TYPE (desc))                                          \
    {                                                                          \
      CASE_TYPE_KIND (name, BT_INTEGER, int)                                   \
      CASE_TYPE_KIND (name, BT_UNSIGNED, uint)                                 \
    case BT_REAL:                                                              \
      switch (GFC_DESCRIPTOR_KIND (desc))                                      \
	{                                                                      \
	case 4:                                                                \
	  opr = (opr_t) name##_float;                                          \
	  break;                                                               \
	case 8:                                                                \
	  opr = (opr_t) name##_double;                                         \
	  break;                                                               \
	default:                                                               \
	  caf_runtime_error ("" #name                                          \
			     " not available for type/kind combination");      \
	  opr = NULL; /* Prevent false warning.  */                            \
	}                                                                      \
      break;                                                                   \
    default:                                                                   \
      caf_runtime_error ("" #name " not available for type/kind combination"); \
      opr = NULL; /* Prevent false warning.  */                                \
    }

void
_gfortran_caf_co_sum (gfc_descriptor_t *desc, int result_image, int *stat,
		      char *errmsg __attribute__ ((unused)),
		      size_t errmsg_len __attribute__ ((unused)))
{
  int mapped_index = -1, this_image_index;
  opr_t opr;

  if (stat)
    *stat = 0;

  /* If result_image == 0 then allreduce is wanted, i.e. mapped_index = -1.  */
  if (result_image
      && !check_map_team (&mapped_index, &this_image_index, result_image, NULL,
			  NULL, stat))
    return;

  SWITCH_TYPE_KIND (sum)

  collsub_reduce_array (desc, mapped_index, opr, 0, 0);
}

void
_gfortran_caf_co_min (gfc_descriptor_t *desc, int result_image, int *stat,
		      char *errmsg __attribute__ ((unused)),
		      int a_len __attribute__ ((unused)),
		      size_t errmsg_len __attribute__ ((unused)))
{
  int mapped_index = -1, this_image_index;
  opr_t opr;

  if (stat)
    *stat = 0;
  /* If result_image == 0 then allreduce is wanted, i.e. mapped_index = -1.  */
  if (result_image
      && !check_map_team (&mapped_index, &this_image_index, result_image, NULL,
			  NULL, stat))
    return;

  SWITCH_TYPE_KIND (min)

  collsub_reduce_array (desc, mapped_index, opr, 0, 0);
}

void
_gfortran_caf_co_max (gfc_descriptor_t *desc, int result_image, int *stat,
		      char *errmsg __attribute__ ((unused)),
		      int a_len __attribute__ ((unused)),
		      size_t errmsg_len __attribute__ ((unused)))
{
  int mapped_index = -1, this_image_index;
  opr_t opr;

  if (stat)
    *stat = 0;
  /* If result_image == 0 then allreduce is wanted, i.e. mapped_index = -1.  */
  if (result_image
      && !check_map_team (&mapped_index, &this_image_index, result_image, NULL,
			  NULL, stat))
    return;

  SWITCH_TYPE_KIND (max)

  collsub_reduce_array (desc, mapped_index, opr, 0, 0);
}

void
_gfortran_caf_co_reduce (gfc_descriptor_t *desc, void *(*opr) (void *, void *),
			 int opr_flags, int result_image, int *stat,
			 char *errmsg __attribute__ ((unused)), int desc_len,
			 size_t errmsg_len __attribute__ ((unused)))
{
  int mapped_index = -1, this_image_index;

  if (stat)
    *stat = 0;

  /* If result_image == 0 then allreduce is wanted, i.e. mapped_index = -1.  */
  if (result_image
      && !check_map_team (&mapped_index, &this_image_index, result_image, NULL,
			  NULL, stat))
    return;

  collsub_reduce_array (desc, mapped_index, opr, opr_flags, desc_len);
}

void
_gfortran_caf_register_accessor (const int hash, getter_t accessor)
{
  if (accessor_hash_table_state == AHT_UNINITIALIZED)
    {
      aht_cap = 16;
      accessor_hash_table = calloc (aht_cap, sizeof (struct accessor_hash_t));
      accessor_hash_table_state = AHT_OPEN;
    }
  if (aht_size == aht_cap)
    {
      aht_cap += 16;
      accessor_hash_table = realloc (accessor_hash_table,
				     aht_cap * sizeof (struct accessor_hash_t));
    }
  if (accessor_hash_table_state == AHT_PREPARED)
    {
      accessor_hash_table_state = AHT_OPEN;
    }
  accessor_hash_table[aht_size].hash = hash;
  accessor_hash_table[aht_size].u.getter = accessor;
  ++aht_size;
}

static int
hash_compare (const struct accessor_hash_t *lhs,
	      const struct accessor_hash_t *rhs)
{
  return lhs->hash < rhs->hash ? -1 : (lhs->hash > rhs->hash ? 1 : 0);
}

void
_gfortran_caf_register_accessors_finish (void)
{
  if (accessor_hash_table_state == AHT_PREPARED
      || accessor_hash_table_state == AHT_UNINITIALIZED)
    return;

  qsort (accessor_hash_table, aht_size, sizeof (struct accessor_hash_t),
	 (int (*) (const void *, const void *)) hash_compare);
  accessor_hash_table_state = AHT_PREPARED;
}

int
_gfortran_caf_get_remote_function_index (const int hash)
{
  if (accessor_hash_table_state != AHT_PREPARED)
    {
      caf_runtime_error ("the accessor hash table is not prepared.");
    }

  struct accessor_hash_t cand;
  cand.hash = hash;
  struct accessor_hash_t *f
    = bsearch (&cand, accessor_hash_table, aht_size,
	       sizeof (struct accessor_hash_t),
	       (int (*) (const void *, const void *)) hash_compare);

  int index = f ? f - accessor_hash_table : -1;
  return index;
}

void
_gfortran_caf_get_from_remote (
  caf_token_t token, const gfc_descriptor_t *opt_src_desc,
  const size_t *opt_src_charlen, const int image_index,
  const size_t dst_size __attribute__ ((unused)), void **dst_data,
  size_t *opt_dst_charlen, gfc_descriptor_t *opt_dst_desc,
  const bool may_realloc_dst, const int getter_index, void *add_data,
  const size_t add_data_size __attribute__ ((unused)), int *stat,
  caf_team_t *team, int *team_number)
{
  caf_shmem_token_t shmem_token = TOKEN (token);
  void *src_ptr;
  int32_t free_buffer;
  int remote_image_index, this_image_index;
  void *dst_ptr = opt_dst_desc ? (void *)opt_dst_desc : dst_data;
  void *old_dst_data_ptr = NULL, *old_src_data_ptr = NULL;
  struct caf_shmem_token cb_token = {add_data, NULL, add_data, 0, ~0, false};

  if (stat)
    *stat = 0;

  if (!check_map_team (&remote_image_index, &this_image_index, image_index,
		       team, team_number, stat))
    return;

  /* Compute the address only after team's mapping has taken place.  */
  src_ptr = shmem_token->base + remote_image_index * shmem_token->image_size;
  if (opt_src_desc)
    {
      old_src_data_ptr = opt_src_desc->base_addr;
      ((gfc_descriptor_t *) opt_src_desc)->base_addr = src_ptr;
      src_ptr = (void *) opt_src_desc;
    }

  if (opt_dst_desc && !may_realloc_dst)
    {
      old_dst_data_ptr = opt_dst_desc->base_addr;
      opt_dst_desc->base_addr = NULL;
    }

  accessor_hash_table[getter_index].u.getter (add_data, &image_index, dst_ptr,
					      &free_buffer, src_ptr, &cb_token,
					      0, opt_dst_charlen,
					      opt_src_charlen);
  if (opt_dst_desc && old_dst_data_ptr && !may_realloc_dst
      && opt_dst_desc->base_addr != old_dst_data_ptr)
    {
      size_t dsize = opt_dst_desc->span;
      for (int i = 0; i < GFC_DESCRIPTOR_RANK (opt_dst_desc); ++i)
	dsize *= GFC_DESCRIPTOR_EXTENT (opt_dst_desc, i);
      memcpy (old_dst_data_ptr, opt_dst_desc->base_addr, dsize);
      free (opt_dst_desc->base_addr);
      opt_dst_desc->base_addr = old_dst_data_ptr;
    }

  if (old_src_data_ptr)
    ((gfc_descriptor_t *) opt_src_desc)->base_addr = old_src_data_ptr;
}

int32_t
_gfortran_caf_is_present_on_remote (caf_token_t token, const int image_index,
				    const int present_index, void *add_data,
				    const size_t add_data_size
				    __attribute__ ((unused)))
{
  /* Unregistered tokens are always not present.  */
  if (!token)
    return 0;

  caf_shmem_token_t shmem_token = TOKEN (token);
  int32_t result;
  struct caf_shmem_token cb_token = {add_data, NULL, add_data, 0, ~0, false};
  void *src_ptr, *arg;
  int remote_image_index, this_image_index;
  GFC_FULL_ARRAY_DESCRIPTOR (GFC_MAX_DIMENSIONS, void) temp_desc;

  if (!check_map_team (&remote_image_index, &this_image_index, image_index,
		       NULL, NULL, NULL))
    return 0;

  src_ptr = shmem_token->base + remote_image_index * shmem_token->image_size;
  if (shmem_token->desc)
    {
      memcpy (&temp_desc, shmem_token->desc,
	      sizeof (gfc_descriptor_t)
		+ GFC_DESCRIPTOR_RANK (shmem_token->desc)
		    * sizeof (descriptor_dimension));
      temp_desc.base_addr = src_ptr;
      arg = &temp_desc;
    }
  else
    arg = &src_ptr;

  accessor_hash_table[present_index].u.is_present (add_data, &image_index,
						   &result, arg, &cb_token, 0);

  return result;
}

void
_gfortran_caf_send_to_remote (
  caf_token_t token, gfc_descriptor_t *opt_dst_desc,
  const size_t *opt_dst_charlen, const int image_index,
  const size_t src_size __attribute__ ((unused)), const void *src_data,
  const size_t *opt_src_charlen, const gfc_descriptor_t *opt_src_desc,
  const int accessor_index, void *add_data,
  const size_t add_data_size __attribute__ ((unused)), int *stat,
  caf_team_t *team, int *team_number)
{
  caf_shmem_token_t shmem_token = TOKEN (token);
  void *dst_ptr, *dst_data_ptr, *old_dst_data_ptr = NULL;
  const void *src_ptr = opt_src_desc ? (void *) opt_src_desc : src_data;
  struct caf_shmem_token cb_token = {add_data, NULL, add_data, 0, ~0, false};
  int remote_image_index, this_image_index;
  GFC_FULL_ARRAY_DESCRIPTOR (GFC_MAX_DIMENSIONS, void) temp_src_desc;

  if (stat)
    *stat = 0;

  if (!check_map_team (&remote_image_index, &this_image_index, image_index,
		       team, team_number, stat))
    return;

  dst_data_ptr = dst_ptr
    = shmem_token->base + remote_image_index * shmem_token->image_size;
  if (opt_dst_desc)
    {
      old_dst_data_ptr = opt_dst_desc->base_addr;
      ((gfc_descriptor_t *) opt_dst_desc)->base_addr = dst_ptr;
      dst_ptr = (void *) opt_dst_desc;
    }

  /* Try to detect copy to self, with overlapping data segment.  */
  if (opt_src_desc && remote_image_index == this_image_index)
    {
      size_t src_data_span = GFC_DESCRIPTOR_SIZE (opt_src_desc);
      for (int d = 0; d < GFC_DESCRIPTOR_RANK (opt_src_desc); d++)
	src_data_span *= GFC_DESCRIPTOR_EXTENT (opt_src_desc, d);
      if (GFC_DESCRIPTOR_DATA (opt_src_desc) >= dst_data_ptr
	  && dst_data_ptr <= GFC_DESCRIPTOR_DATA (opt_src_desc) + src_data_span)
	{
	  src_ptr = __builtin_alloca (src_data_span);
	  if (!src_ptr)
	    {
	      caf_internal_error ("Out of stack in coarray send (dst[...] = "
				  "...) expression. Increase stacksize!",
				  stat, NULL, 0);
	      return;
	    }
	  memcpy ((void *) src_ptr, GFC_DESCRIPTOR_DATA (opt_src_desc),
		  src_data_span);
	  memcpy (&temp_src_desc, opt_src_desc,
		  sizeof (gfc_descriptor_t)
		    + sizeof (descriptor_dimension)
			* GFC_DESCRIPTOR_RANK (opt_src_desc));
	  temp_src_desc.base_addr = (void *) src_ptr;
	  src_ptr = (void *) &temp_src_desc;
	}
    }

  accessor_hash_table[accessor_index].u.receiver (add_data, &image_index,
						  dst_ptr, src_ptr, &cb_token,
						  0, opt_dst_charlen,
						  opt_src_charlen);

  if (old_dst_data_ptr)
    ((gfc_descriptor_t *) opt_dst_desc)->base_addr = old_dst_data_ptr;
}

void
_gfortran_caf_transfer_between_remotes (
  caf_token_t dst_token, gfc_descriptor_t *opt_dst_desc,
  size_t *opt_dst_charlen, const int dst_image_index,
  const int dst_access_index, void *dst_add_data,
  const size_t dst_add_data_size __attribute__ ((unused)),
  caf_token_t src_token, const gfc_descriptor_t *opt_src_desc,
  const size_t *opt_src_charlen, const int src_image_index,
  const int src_access_index, void *src_add_data,
  const size_t src_add_data_size __attribute__ ((unused)),
  const size_t src_size, const bool scalar_transfer, int *dst_stat,
  int *src_stat, caf_team_t *dst_team, int *dst_team_number,
  caf_team_t *src_team, int *src_team_number)
{
  static const char *out_of_stack_errmsg
    = "Out of stack in coarray transfer between remotes (dst[...] = "
      "src[...]) expression. Increase stacksize!";
  caf_shmem_token_t src_shmem_token = TOKEN (src_token),
		    dst_shmem_token = TOKEN (dst_token);
  void *src_ptr, *old_src_data_ptr = NULL;
  int32_t free_buffer;
  void *dst_ptr, *old_dst_data_ptr = NULL;
  void *transfer_ptr, *buffer;
  GFC_FULL_ARRAY_DESCRIPTOR (GFC_MAX_DIMENSIONS, void) *transfer_desc = NULL;
  struct caf_shmem_token cb_token
    = {src_add_data, NULL, src_add_data, 0, ~0, false};
  int remote_image_index, this_image_index;

  if (src_stat)
    *src_stat = 0;

  if (!check_map_team (&remote_image_index, &this_image_index, src_image_index,
		       src_team, src_team_number, src_stat))
    return;

  if (!scalar_transfer)
    {
      const size_t desc_size = sizeof (*transfer_desc);
      transfer_desc = __builtin_alloca (desc_size);
      if (!transfer_desc)
	{
	  caf_internal_error (out_of_stack_errmsg, src_stat, NULL, 0);
	  return;
	}
      memset (transfer_desc, 0, desc_size);
      transfer_ptr = transfer_desc;
    }
  else if (opt_dst_charlen)
    {
      transfer_ptr = __builtin_alloca (*opt_dst_charlen * src_size);
      if (!transfer_ptr)
	{
	  caf_internal_error (out_of_stack_errmsg, src_stat, NULL, 0);
	  return;
	}
    }
  else
    {
      buffer = NULL;
      transfer_ptr = &buffer;
    }

  src_ptr
    = src_shmem_token->base + remote_image_index * src_shmem_token->image_size;
  if (opt_src_desc)
    {
      old_src_data_ptr = opt_src_desc->base_addr;
      ((gfc_descriptor_t *) opt_src_desc)->base_addr = src_ptr;
      src_ptr = (void *) opt_src_desc;
    }

  accessor_hash_table[src_access_index].u.getter (
    src_add_data, &src_image_index, transfer_ptr, &free_buffer, src_ptr,
    &cb_token, 0, opt_dst_charlen, opt_src_charlen);

  if (old_src_data_ptr)
    ((gfc_descriptor_t *) opt_src_desc)->base_addr = old_src_data_ptr;

  if (dst_stat)
    *dst_stat = 0;

  if (!check_map_team (&remote_image_index, &this_image_index, dst_image_index,
		       dst_team, dst_team_number, dst_stat))
    return;

  if (scalar_transfer)
    transfer_ptr = *(void **) transfer_ptr;

  dst_ptr
    = dst_shmem_token->base + remote_image_index * dst_shmem_token->image_size;
  if (opt_dst_desc)
    {
      old_dst_data_ptr = opt_dst_desc->base_addr;
      ((gfc_descriptor_t *) opt_dst_desc)->base_addr = dst_ptr;
      dst_ptr = (void *) opt_dst_desc;
    }

  cb_token.memptr = cb_token.base = dst_add_data;
  accessor_hash_table[dst_access_index].u.receiver (dst_add_data,
						    &dst_image_index, dst_ptr,
						    transfer_ptr, &cb_token, 0,
						    opt_dst_charlen,
						    opt_src_charlen);

  if (old_dst_data_ptr)
    ((gfc_descriptor_t *) opt_dst_desc)->base_addr = old_dst_data_ptr;

  if (free_buffer)
    free (transfer_desc ? transfer_desc->base_addr : transfer_ptr);
}

#define GET_ATOM                                                               \
  caf_shmem_token_t shmem_token = TOKEN (token);                               \
  int remote_image_index, this_image_index;                                    \
  if (stat)                                                                    \
    *stat = 0;                                                                 \
  if (!image_index)                                                            \
    image_index = this_image.image_num + 1;                                    \
  if (!check_map_team (&remote_image_index, &this_image_index, image_index,    \
		       NULL, NULL, stat))                                      \
    return;                                                                    \
  assert (kind == 4);                                                          \
  uint32_t *atom                                                               \
    = (uint32_t *) (shmem_token->base                                          \
		    + remote_image_index * shmem_token->image_size + offset)

void
_gfortran_caf_atomic_define (caf_token_t token, size_t offset, int image_index,
			     void *value, int *stat,
			     int type __attribute__ ((unused)), int kind)
{
  GET_ATOM;

  __atomic_store (atom, (uint32_t *) value, __ATOMIC_SEQ_CST);
}

void
_gfortran_caf_atomic_ref (caf_token_t token, size_t offset, int image_index,
			  void *value, int *stat,
			  int type __attribute__ ((unused)), int kind)
{
  GET_ATOM;

  __atomic_load (atom, (uint32_t *) value, __ATOMIC_SEQ_CST);
}

void
_gfortran_caf_atomic_cas (caf_token_t token, size_t offset, int image_index,
			  void *old, void *compare, void *new_val, int *stat,
			  int type __attribute__ ((unused)), int kind)
{
  GET_ATOM;

  *(uint32_t *) old = *(uint32_t *) compare;
  (void) __atomic_compare_exchange_n (atom, (uint32_t *) old,
				      *(uint32_t *) new_val, false,
				      __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}

void
_gfortran_caf_atomic_op (int op, caf_token_t token, size_t offset,
			 int image_index, void *value, void *old, int *stat,
			 int type __attribute__ ((unused)), int kind)
{
  GET_ATOM;

  uint32_t res;

  switch (op)
    {
    case GFC_CAF_ATOMIC_ADD:
      res = __atomic_fetch_add (atom, *(uint32_t *) value, __ATOMIC_SEQ_CST);
      break;
    case GFC_CAF_ATOMIC_AND:
      res = __atomic_fetch_and (atom, *(uint32_t *) value, __ATOMIC_SEQ_CST);
      break;
    case GFC_CAF_ATOMIC_OR:
      res = __atomic_fetch_or (atom, *(uint32_t *) value, __ATOMIC_SEQ_CST);
      break;
    case GFC_CAF_ATOMIC_XOR:
      res = __atomic_fetch_xor (atom, *(uint32_t *) value, __ATOMIC_SEQ_CST);
      break;
    default:
      __builtin_unreachable ();
    }

  if (old)
    *(uint32_t *) old = res;
}

#define GET_EVENT(token_, index_, image_index_)                                \
  ((event_t *) (((caf_shmem_token_t) token_)->base                             \
		+ ((caf_shmem_token_t) token_)->image_size * image_index_      \
		+ sizeof (event_t) * index_))

void
_gfortran_caf_event_post (caf_token_t token, size_t index, int image_index,
			  int *stat, char *errmsg __attribute__ ((unused)),
			  size_t errmsg_len __attribute__ ((unused)))
{
  int remote_image_index, this_image_index;

  if (stat)
    *stat = 0;

  /* When image_index is zero, access this image's event.  */
  if (!image_index)
    image_index = this_image.image_num + 1;

  if (!check_map_team (&remote_image_index, &this_image_index, image_index,
		       NULL, NULL, stat))
    return;

  volatile event_t *event = GET_EVENT (token, index, remote_image_index);

  lock_event (&local->si);
  --(*event);
  event_post (&local->si);
  unlock_event (&local->si);
}

void
_gfortran_caf_event_wait (caf_token_t token, size_t index, int until_count,
			  int *stat, char *errmsg __attribute__ ((unused)),
			  size_t errmsg_len __attribute__ ((unused)))
{
  int remote_image_index, this_image_index;

  if (stat)
    *stat = 0;

  if (!check_map_team (&remote_image_index, &this_image_index, 1, NULL, NULL,
		       stat))
    return;

  volatile event_t *event = GET_EVENT (token, index, this_image_index);
  event_t val;

  lock_event (&local->si);
  val = (*event += until_count);
  if (val > 0) /* Move the invariant out of the loop.  */
    while (*event > 0)
      event_wait (&local->si);
  unlock_event (&local->si);

  if (stat)
    *stat = 0;
}

void
_gfortran_caf_event_query (caf_token_t token, size_t index, int image_index,
			   int *count, int *stat)
{
  int remote_image_index, this_image_index;

  if (stat)
    *stat = 0;

  /* When image_index is zero, access this image's event.  */
  if (!image_index)
    image_index = this_image.image_num + 1;

  if (!check_map_team (&remote_image_index, &this_image_index, image_index,
		       NULL, NULL, stat))
    return;

  volatile event_t *event = GET_EVENT (token, index, remote_image_index);

  lock_event (&local->si);
  *count = *event;
  unlock_event (&local->si);

  if (*count < 0)
    *count = -*count;
}

void
_gfortran_caf_lock (caf_token_t token, size_t index, int image_index,
		    int *acquired_lock, int *stat, char *errmsg,
		    size_t errmsg_len)
{
  const char *msg = "Already locked";
#if defined(WIN32) || defined(__CYGWIN__)
  const size_t lock_index
    = image_index * caf_current_team->u.image_info->image_count.count + index;
#else
  const size_t lock_index = index;
  (void) image_index; // Prevent unused warnings.
#endif
  lock_t *lock = &((lock_t *) MEMTOK (token))[lock_index];
  int res;

  res = acquired_lock ? caf_shmem_mutex_trylock (lock)
		      : caf_shmem_mutex_lock (lock);

  if (stat)
    *stat = res == EBUSY ? GFC_STAT_LOCKED : 0;

  if (acquired_lock)
    {
      *acquired_lock = (int) (res == 0);
      return;
    }

  if (!res)
    return;

  if (stat)
    {
      if (errmsg_len > 0)
	{
	  size_t len = (sizeof (msg) > errmsg_len) ? errmsg_len : sizeof (msg);
	  memcpy (errmsg, msg, len);
	  if (errmsg_len > len)
	    memset (&errmsg[len], ' ', errmsg_len - len);
	}
      return;
    }
  _gfortran_caf_error_stop_str (msg, strlen (msg), false);
}

void
_gfortran_caf_unlock (caf_token_t token, size_t index, int image_index,
		      int *stat, char *errmsg, size_t errmsg_len)
{
  const char *msg = "Variable is not locked";
#if defined(WIN32) || defined(__CYGWIN__)
  const size_t lock_index
    = image_index * caf_current_team->u.image_info->image_count.count + index;
#else
  const size_t lock_index = index;
  (void) image_index; // Prevent unused warnings.
#endif
  lock_t *lock = &((lock_t *) MEMTOK (token))[lock_index];
  int res;

  res = caf_shmem_mutex_unlock (lock);

  if (res == 0)
    {
      if (stat)
	*stat = 0;
      return;
    }

  if (stat && res == EPERM)
    {
      /* res == EPERM means that the lock is locked.  Now figure, if by us by
	 trying to lock it or by other image, which fails.  */
      res = caf_shmem_mutex_trylock (lock);
      if (res == EBUSY)
	*stat = GFC_STAT_LOCKED_OTHER_IMAGE;
      else
	{
	  *stat = GFC_STAT_UNLOCKED;
	  caf_shmem_mutex_unlock (lock);
	}

      if (errmsg_len > 0)
	{
	  size_t len = (sizeof (msg) > errmsg_len) ? errmsg_len : sizeof (msg);
	  memcpy (errmsg, msg, len);
	  if (errmsg_len > len)
	    memset (&errmsg[len], ' ', errmsg_len - len);
	}
      return;
    }
  _gfortran_caf_error_stop_str (msg, strlen (msg), false);
}

/* Reference the libraries implementation.  */
extern void _gfortran_random_seed_i4 (int32_t *size, gfc_array_i4 *put,
				      gfc_array_i4 *get);

void
_gfortran_caf_random_init (bool repeatable, bool image_distinct)
{
  static struct
  {
    int32_t *base_addr;
    size_t offset;
    dtype_type dtype;
    index_type span;
    descriptor_dimension dim[1];
  } rand_seed;
  static bool rep_needs_init = true, arr_needs_init = true;
  static int32_t seed_size;

  if (arr_needs_init)
    {
      _gfortran_random_seed_i4 (&seed_size, NULL, NULL);
      memset (&rand_seed, 0,
	      sizeof (gfc_array_i4) + sizeof (descriptor_dimension));
      rand_seed.base_addr
	= malloc (seed_size * sizeof (int32_t)); // because using seed_i4
      rand_seed.offset = -1;
      rand_seed.dtype.elem_len = sizeof (int32_t);
      rand_seed.dtype.rank = 1;
      rand_seed.dtype.type = BT_INTEGER;
      rand_seed.span = 0;
      rand_seed.dim[0].lower_bound = 1;
      rand_seed.dim[0]._ubound = seed_size;
      rand_seed.dim[0]._stride = 1;

      arr_needs_init = false;
    }

  if (repeatable)
    {
      if (rep_needs_init)
	{
	  int32_t lcg_seed = 57911963;
	  if (image_distinct)
	    {
	      lcg_seed *= this_image.image_num;
	    }
	  int32_t *curr = rand_seed.base_addr;
	  for (int i = 0; i < seed_size; ++i)
	    {
	      const int32_t a = 16087;
	      const int32_t m = INT32_MAX;
	      const int32_t q = 127773;
	      const int32_t r = 2836;
	      lcg_seed = a * (lcg_seed % q) - r * (lcg_seed / q);
	      if (lcg_seed <= 0)
		lcg_seed += m;
	      *curr = lcg_seed;
	      ++curr;
	    }
	  rep_needs_init = false;
	}
      _gfortran_random_seed_i4 (NULL, (gfc_array_i4 *) &rand_seed, NULL);
    }
  else if (image_distinct)
    {
      _gfortran_random_seed_i4 (NULL, NULL, NULL);
    }
  else
    {
      if (this_image.image_num == 0)
	{
	  _gfortran_random_seed_i4 (NULL, NULL, (gfc_array_i4 *) &rand_seed);
	  collsub_broadcast_array ((gfc_descriptor_t *) &rand_seed, 0);
	}
      else
	{
	  collsub_broadcast_array ((gfc_descriptor_t *) &rand_seed, 0);
	  _gfortran_random_seed_i4 (NULL, (gfc_array_i4 *) &rand_seed, NULL);
	}
    }
}

void
_gfortran_caf_form_team (int team_no, caf_team_t *team, int *new_index,
			 int *stat, char *errmsg, size_t errmsg_len)
{
  const char new_index_out_of_range[]
    = "The NEW_INDEX in a FORM TEAM has to in (0, num_images()].";
  const char team_no_negativ[]
    = "The team number in FORM TEAM has to be positive.";
  const char alloc_fail_msg[] = "Failed to allocate team";
  const char non_unique_image_ids[]
    = "The NEW_INDEX of FORM TEAMs has to be unique.";
  const char cannot_assign_index[]
    = "Can not assign new image index in FORM TEAM.";
  static int image_size_shift = -1;
  static int teams_count = 0;
  caf_shmem_team_t t;
  bool created;
  memid tmemid;

  if (image_size_shift < 0)
    image_size_shift = (int) round (log2 (local->total_num_images));
  if (stat)
    *stat = 0;

  CHECK_TEAM_INTEGRITY (caf_current_team);

  if (new_index
      && (*new_index <= 0
	  || *new_index > caf_current_team->u.image_info->image_count.count))
    {
      caf_internal_error (new_index_out_of_range, stat, errmsg, errmsg_len);
      return;
    }
  if (team_no <= 0)
    {
      caf_internal_error (team_no_negativ, stat, errmsg, errmsg_len);
      return;
    }

  *team = malloc (sizeof (struct caf_shmem_team));
  if (unlikely (*team == NULL))
    {
      caf_internal_error (alloc_fail_msg, stat, errmsg, errmsg_len);
      return;
    }
  t = *((caf_shmem_team_t *) team);

  allocator_lock (&local->ai.alloc);
  if (caf_current_team->team_no == -1)
    tmemid = team_no + teams_count;
  else
    tmemid = (caf_current_team->u.image_info->lastmemid << image_size_shift)
	     + team_no + teams_count;
  ++teams_count;
  *t = (struct caf_shmem_team) {
    caf_teams_formed,
    team_no,
    -1,
    0,
    NULL,
    {alloc_get_memory_by_id_created (
      &local->ai,
      sizeof (struct shmem_image_info)
	+ caf_current_team->u.image_info->image_count.count * sizeof (int),
      -tmemid, &created)}};

  if (created)
    {
      counter_barrier_init (&t->u.image_info->image_count, 0);
      collsub_init_supervisor (&t->u.image_info->collsub,
			       alloc_get_allocator (&local->ai), 0);
      t->u.image_info->team_parent_id = caf_current_team->team_no;
      t->u.image_info->team_id = team_no;
      t->u.image_info->image_map_size = 0;
      t->u.image_info->num_term_images = 0;
      t->u.image_info->lastmemid = tmemid;
      /* Initialize a freshly created image_map with -1.  */
      for (int i = 0; i < caf_current_team->u.image_info->image_count.count;
	   ++i)
	t->u.image_info->image_map[i] = -1;
    }
  counter_barrier_init_add (&t->u.image_info->image_count, 1);
  counter_barrier_init_add (&t->u.image_info->collsub.barrier, 1);
  allocator_unlock (&local->ai.alloc);

  if (new_index)
    {
      int old_id;

      t->index = *new_index - 1;
      old_id = __atomic_exchange_n (&t->u.image_info->image_map[t->index],
				    this_image.image_num, __ATOMIC_SEQ_CST);
      if (old_id != -1)
	{
	  caf_internal_error (non_unique_image_ids, stat, errmsg, errmsg_len);
	  return;
	}

      __atomic_fetch_add (&t->u.image_info->image_map_size, 1,
			  __ATOMIC_SEQ_CST);
    }
  else
    {
      int im, cnt;
      int exp;

      __atomic_fetch_add (&t->u.image_info->image_map_size, 1,
			  __ATOMIC_SEQ_CST);
      sync_team (caf_current_team);

      cnt = t->u.image_info->image_map_size;
      /* Try to map the source team's images linearly into the domain of the
	 new team.  This works for scattered teams distributions.  I.e. when a
	 set of images is distritubed in this way:
	 Image no: 1 2 3 4 5 6
	 New team: 1 2 1 2 1 2
	 but not for:
	 Image no: 1 2 3 4 5 6
	 New team: 1 1 1 2 2 2
      */
      im = caf_current_team->index * cnt
	   / caf_current_team->u.image_info->image_count.count;
      /* Map our old index into the domain of the new team's size.  */
      do
	{
	  /* (Re-)set exp.  */
	  exp = -1;
	  if (__atomic_compare_exchange_n (&t->u.image_info->image_map[im],
					   &exp, this_image.image_num, false,
					   __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
	    {
	      t->index = im;
	      goto form_team_finish;
	    }
	  /* Find a free new_index in the newly formed team for this image.
	     There no longer is any order to the teams.  */
	  ++im;
	  if (im >= t->u.image_info->image_map_size)
	    im = 0;
	  --cnt;
	}
      while (cnt > 0);

      caf_internal_error (cannot_assign_index, stat, errmsg, errmsg_len);
      return;
    }

form_team_finish:
  sync_team (caf_current_team);

  caf_teams_formed = t;
}

void
_gfortran_caf_change_team (caf_team_t team, int *stat,
			   char *errmsg __attribute__ ((unused)),
			   size_t errmsg_len __attribute__ ((unused)))
{
  caf_shmem_team_t t = (caf_shmem_team_t) team;

  if (stat)
    *stat = 0;

  if (t == caf_teams_formed)
    caf_teams_formed = t->parent;
  else
    for (caf_shmem_team_t p = caf_teams_formed; p; p = p->parent)
      if (p->parent == t)
	{
	  p->parent = t->parent;
	  break;
	}

  t->parent = caf_current_team;
  t->parent_teams_last_active_memid = next_memid;
  next_memid = (t->u.image_info->team_parent_id != -1
		  ? (((memid) t->u.image_info->team_parent_id) << 48)
		  : 0)
	       | (((memid) t->u.image_info->team_id) << 32) | 1;
  caf_current_team = t;
  sync_team (caf_current_team);
}

void
_gfortran_caf_end_team (int *stat, char *errmsg, size_t errmsg_len)
{
  caf_shmem_team_t t = caf_current_team;

  if (stat)
    *stat = 0;

  caf_current_team = caf_current_team->parent;
  next_memid = t->parent_teams_last_active_memid;
  sync_team (t);

  for (struct coarray_allocated *ca = t->allocated; ca;)
    {
      struct coarray_allocated *nca = ca->next;
      _gfortran_caf_deregister ((caf_token_t *) &ca->token,
				CAF_DEREGTYPE_COARRAY_DEALLOCATE_ONLY, stat,
				errmsg, errmsg_len);
      free (ca);
      ca = nca;
    }
  t->allocated = NULL;
  t->parent = caf_teams_formed;
  caf_teams_formed = t;
}

void
_gfortran_caf_sync_team (caf_team_t team, int *stat, char *errmsg,
			 size_t errmsg_len)
{
  caf_shmem_team_t team_to_sync = (caf_shmem_team_t) team;
  caf_shmem_team_t active_team = caf_current_team;

  if (stat)
    *stat = 0;

  /* Check if team to sync is a child of the current team, aka not changed to
     yet.  */
  if (team_to_sync->u.image_info->team_parent_id != active_team->team_no)
    for (; active_team && active_team != team_to_sync;
	 active_team = active_team->parent)
      ;

  CHECK_TEAM_INTEGRITY (active_team);

  if (!active_team)
    {
      caf_internal_error ("SYNC TEAM: Called on team different from current, "
			  "or ancestor, or child",
			  stat, errmsg, errmsg_len);
      return;
    }

  sync_team (team_to_sync);
}

int
_gfortran_caf_team_number (caf_team_t team)
{
  return team ? ((caf_shmem_team_t) team)->u.image_info->team_id
	      : caf_current_team->u.image_info->team_id;
}

caf_team_t
_gfortran_caf_get_team (int32_t *level)
{
  if (!level)
    return caf_current_team;

  switch ((caf_team_level_t) *level)
    {
    case CAF_INITIAL_TEAM:
      return caf_initial_team;
    case CAF_PARENT_TEAM:
      return caf_current_team->parent ? caf_current_team->parent
				      : caf_current_team;
    case CAF_CURRENT_TEAM:
      return caf_current_team;
    default:
      caf_runtime_error ("Illegal value for GET_TEAM");
    }
  return NULL; /* To prevent any warnings.  */
}
