/* Rewrite the expression tree for coarrays.
   Copyright (C) 2010-2025 Free Software Foundation, Inc.
   Contributed by Andre Vehreschild.

This file is part of GCC.

GCC 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.

GCC 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.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

/* Rewrite the expression for coarrays where needed:
   - coarray indexing operations need the indexing expression put into a
     routine callable on the remote image

   This rewriter is meant to used for non-optimisational expression tree
   rewrites.  When implementing early optimisation it is recommended to
   do this in frontend-passes.cc.
*/

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "options.h"
#include "bitmap.h"
#include "gfortran.h"

/* The code tree element that is currently processed.  */
static gfc_code **current_code;

/* Code that is inserted into the current caf_accessor at the beginning.  */
static gfc_code *caf_accessor_prepend = nullptr;

static bool caf_on_lhs = false;

static int caf_sym_cnt = 0;

static gfc_array_spec *
get_arrayspec_from_expr (gfc_expr *expr)
{
  gfc_array_spec *src_as, *dst_as = NULL;
  gfc_ref *ref;
  gfc_array_ref mod_src_ar;
  int dst_rank = 0;

  if (expr->rank == 0)
    return NULL;

  if (expr->expr_type == EXPR_FUNCTION)
    return gfc_copy_array_spec (expr->symtree->n.sym->as);

  /* Follow any component references.  */
  if (expr->expr_type == EXPR_VARIABLE || expr->expr_type == EXPR_CONSTANT)
    {
      if (expr->symtree)
	src_as = expr->symtree->n.sym->as;
      else
	src_as = NULL;

      for (ref = expr->ref; ref; ref = ref->next)
	{
	  switch (ref->type)
	    {
	    case REF_COMPONENT:
	      src_as = ref->u.c.component->as;
	      continue;

	    case REF_SUBSTRING:
	    case REF_INQUIRY:
	      continue;

	    case REF_ARRAY:
	      switch (ref->u.ar.type)
		{
		case AR_ELEMENT:
		  src_as = NULL;
		  break;
		case AR_SECTION:
		  {
		    if (!dst_as)
		      dst_as = gfc_get_array_spec ();
		    memset (&mod_src_ar, 0, sizeof (gfc_array_ref));
		    mod_src_ar = ref->u.ar;
		    for (int dim = 0; dim < src_as->rank; ++dim)
		      {
			switch (ref->u.ar.dimen_type[dim])
			  {
			  case DIMEN_ELEMENT:
			    gfc_free_expr (mod_src_ar.start[dim]);
			    mod_src_ar.start[dim] = NULL;
			    break;
			  case DIMEN_RANGE:
			    dst_as->lower[dst_rank]
			      = gfc_copy_expr (ref->u.ar.start[dim]);
			    mod_src_ar.start[dst_rank]
			      = gfc_copy_expr (ref->u.ar.start[dim]);
			    if (ref->u.ar.end[dim])
			      {
				dst_as->upper[dst_rank]
				  = gfc_copy_expr (ref->u.ar.end[dim]);
				mod_src_ar.end[dst_rank] = ref->u.ar.end[dim];
				mod_src_ar.stride[dst_rank]
				  = ref->u.ar.stride[dim];
			      }
			    else
			      dst_as->upper[dst_rank]
				= gfc_copy_expr (ref->u.ar.as->upper[dim]);
			    ++dst_rank;
			    break;
			  case DIMEN_STAR:
			    dst_as->lower[dst_rank]
			      = gfc_copy_expr (ref->u.ar.as->lower[dim]);
			    mod_src_ar.start[dst_rank]
			      = gfc_copy_expr (ref->u.ar.start[dim]);
			    if (ref->u.ar.as->upper[dim])
			      {
				dst_as->upper[dst_rank]
				  = gfc_copy_expr (ref->u.ar.as->upper[dim]);
				mod_src_ar.end[dst_rank] = ref->u.ar.end[dim];
				mod_src_ar.stride[dst_rank]
				  = ref->u.ar.stride[dim];
			      }
			    ++dst_rank;
			    break;
			  case DIMEN_VECTOR:
			    dst_as->lower[dst_rank]
			      = gfc_get_constant_expr (BT_INTEGER,
						       gfc_index_integer_kind,
						       &expr->where);
			    mpz_set_ui (dst_as->lower[dst_rank]->value.integer,
					1);
			    mod_src_ar.start[dst_rank]
			      = gfc_copy_expr (ref->u.ar.start[dim]);
			    dst_as->upper[dst_rank]
			      = gfc_get_constant_expr (BT_INTEGER,
						       gfc_index_integer_kind,
						       &expr->where);
			    mpz_set (dst_as->upper[dst_rank]->value.integer,
				     ref->u.ar.start[dim]->shape[0]);
			    ++dst_rank;
			    break;
			  case DIMEN_THIS_IMAGE:
			  case DIMEN_UNKNOWN:
			    gcc_unreachable ();
			  }
			if (ref->u.ar.dimen_type[dim] != DIMEN_ELEMENT)
			  mod_src_ar.dimen_type[dst_rank]
			    = ref->u.ar.dimen_type[dim];
		      }
		    dst_as->rank = dst_rank;
		    dst_as->type = AS_EXPLICIT;
		    ref->u.ar = mod_src_ar;
		    ref->u.ar.dimen = dst_rank;
		    break;

		  case AR_UNKNOWN:
		    src_as = NULL;
		    break;

		  case AR_FULL:
		    if (dst_as)
		      /* Prevent memory loss.  */
		      gfc_free_array_spec (dst_as);
		    dst_as = gfc_copy_array_spec (src_as);
		    break;
		  }
		  break;
		}
	    }
	}
    }
  else
    src_as = NULL;

  return dst_as;
}

static void
remove_coarray_from_derived_type (gfc_symbol *base, gfc_namespace *ns,
				  gfc_array_spec *src_as = NULL)
{
  gfc_symbol *derived;
  gfc_symbol *src_derived = base->ts.u.derived;

  if (!src_as)
    src_as = src_derived->as;
  gfc_get_symbol (src_derived->name, ns, &derived);
  derived->attr.flavor = FL_DERIVED;
  derived->attr.alloc_comp = src_derived->attr.alloc_comp;
  if (src_as && src_as->rank != 0)
    {
      base->attr.dimension = 1;
      base->as = gfc_copy_array_spec (src_as);
      base->as->corank = 0;
    }
  for (gfc_component *p = NULL, *c = src_derived->components; c; c = c->next)
    {
      gfc_component *n = gfc_get_component ();
      *n = *c;
      if (n->as)
	n->as = gfc_copy_array_spec (c->as);
      n->backend_decl = NULL;
      n->initializer = NULL;
      n->param_list = NULL;
      if (p)
	p->next = n;
      else
	derived->components = n;

      p = n;
    }
  derived->declared_at = base->declared_at;
  gfc_set_sym_referenced (derived);
  gfc_commit_symbol (derived);
  base->ts.u.derived = derived;
  gfc_commit_symbol (base);
}

static void
convert_coarray_class_to_derived_type (gfc_symbol *base, gfc_namespace *ns)
{
  gfc_symbol *src_derived = CLASS_DATA (base)->ts.u.derived;
  gfc_array_spec *src_as = CLASS_DATA (base)->as;
  const bool attr_allocatable
    = src_as && src_as->rank && src_as->type == AS_DEFERRED;

  base->ts.type = BT_DERIVED;
  base->ts.u.derived = src_derived;

  remove_coarray_from_derived_type (base, ns, src_as);

  base->attr.allocatable = attr_allocatable;
  base->attr.pointer = 0; // Ensure, that it is no pointer.
}

static void
move_coarray_ref (gfc_ref **from, gfc_expr *expr)
{
  int i;
  gfc_ref *to = expr->ref;
  for (; to && to->next; to = to->next)
    ;

  if (!to)
    {
      expr->ref = gfc_get_ref ();
      to = expr->ref;
      to->type = REF_ARRAY;
    }
  gcc_assert (to->type == REF_ARRAY);
  to->u.ar.as = gfc_copy_array_spec ((*from)->u.ar.as);
  to->u.ar.codimen = (*from)->u.ar.codimen;
  to->u.ar.dimen = (*from)->u.ar.dimen;
  to->u.ar.type = AR_FULL;
  to->u.ar.stat = (*from)->u.ar.stat;
  (*from)->u.ar.stat = nullptr;
  to->u.ar.team = (*from)->u.ar.team;
  (*from)->u.ar.team = nullptr;
  to->u.ar.team_type = (*from)->u.ar.team_type;
  (*from)->u.ar.team_type = TEAM_UNSET;
  for (i = 0; i < to->u.ar.dimen; ++i)
    {
      to->u.ar.start[i] = nullptr;
      to->u.ar.end[i] = nullptr;
      to->u.ar.stride[i] = nullptr;
    }
  for (i = (*from)->u.ar.dimen; i < (*from)->u.ar.dimen + (*from)->u.ar.codimen;
       ++i)
    {
      to->u.ar.dimen_type[i] = (*from)->u.ar.dimen_type[i];
      to->u.ar.start[i] = (*from)->u.ar.start[i];
      (*from)->u.ar.start[i] = nullptr;
      to->u.ar.end[i] = (*from)->u.ar.end[i];
      (*from)->u.ar.end[i] = nullptr;
      to->u.ar.stride[i] = (*from)->u.ar.stride[i];
      (*from)->u.ar.stride[i] = nullptr;
    }
  (*from)->u.ar.codimen = 0;
  if ((*from)->u.ar.dimen == 0)
    {
      gfc_ref *nref = (*from)->next;
      (*from)->next = nullptr;
      gfc_free_ref_list (*from);
      *from = nref;
    }
}

static void
fixup_comp_refs (gfc_expr *expr)
{
  bool class_ref = expr->symtree->n.sym->ts.type == BT_CLASS;
  gfc_symbol *type
    = expr->symtree->n.sym->ts.type == BT_DERIVED
	? expr->symtree->n.sym->ts.u.derived
	: (class_ref ? CLASS_DATA (expr->symtree->n.sym)->ts.u.derived
		     : nullptr);
  if (!type)
    return;
  gfc_ref **pref = &(expr->ref);
  for (gfc_ref *ref = expr->ref; ref && type;)
    {
      switch (ref->type)
	{
	case REF_COMPONENT:
	  gfc_find_component (type, ref->u.c.component->name, false, true,
			      pref);
	  if (!*pref)
	    {
	      /* This happens when there were errors previously.  Just don't
		 crash.  */
	      ref = nullptr;
	      break;
	    }
	  if (class_ref)
	    /* Link to the class type to allow for derived type resolution.  */
	    (*pref)->u.c.sym = ref->u.c.sym;
	  (*pref)->next = ref->next;
	  ref->next = NULL;
	  gfc_free_ref_list (ref);
	  ref = (*pref)->next;
	  type = (*pref)->u.c.component->ts.type == BT_DERIVED
		   ? (*pref)->u.c.component->ts.u.derived
		   : ((*pref)->u.c.component->ts.type == BT_CLASS
			? CLASS_DATA ((*pref)->u.c.component)->ts.u.derived
			: nullptr);
	  pref = &(*pref)->next;
	  break;
	case REF_ARRAY:
	  pref = &ref->next;
	  ref = ref->next;
	  break;
	default:
	  gcc_unreachable ();
	  break;
	}
    }
}

static void
split_expr_at_caf_ref (gfc_expr *expr, gfc_namespace *ns,
		       gfc_expr **post_caf_ref_expr, bool for_send)
{
  gfc_ref *caf_ref = NULL;
  gfc_symtree *st;
  gfc_symbol *base;
  gfc_typespec *caf_ts;
  bool created;

  gcc_assert (expr->expr_type == EXPR_VARIABLE);
  caf_ts = &expr->symtree->n.sym->ts;
  if (!expr->symtree->n.sym->attr.codimension)
    {
      /* The coarray is in some component.  Find it.  */
      caf_ref = expr->ref;
      while (caf_ref)
	{
	  if (caf_ref->type == REF_ARRAY && caf_ref->u.ar.codimen != 0)
	    break;
	  if (caf_ref->type == REF_COMPONENT)
	    caf_ts = &caf_ref->u.c.component->ts;
	  caf_ref = caf_ref->next;
	}
    }

  created = !gfc_get_sym_tree (!caf_ref ? expr->symtree->name : "base", ns, &st,
			       false);
  gcc_assert (created);
  st->n.sym->attr.flavor = FL_PARAMETER;
  st->n.sym->attr.dummy = 1;
  st->n.sym->attr.intent = INTENT_IN;
  st->n.sym->ts = *caf_ts;
  st->n.sym->declared_at = expr->where;

  *post_caf_ref_expr = gfc_get_variable_expr (st);
  (*post_caf_ref_expr)->where = expr->where;
  base = (*post_caf_ref_expr)->symtree->n.sym;

  if (!caf_ref)
    {
      (*post_caf_ref_expr)->ref = gfc_get_ref ();
      *(*post_caf_ref_expr)->ref = *expr->ref;
      expr->ref = nullptr;
      move_coarray_ref (&(*post_caf_ref_expr)->ref, expr);
      fixup_comp_refs (expr);

      if (expr->symtree->n.sym->attr.dimension)
	{
	  base->as = gfc_copy_array_spec (expr->symtree->n.sym->as);
	  base->as->corank = 0;
	  base->attr.dimension = 1;
	  base->attr.allocatable = expr->symtree->n.sym->attr.allocatable;
	  base->attr.pointer = expr->symtree->n.sym->attr.pointer
			       || expr->symtree->n.sym->attr.associate_var;
	}
    }
  else
    {
      (*post_caf_ref_expr)->ref = gfc_get_ref ();
      *(*post_caf_ref_expr)->ref = *caf_ref;
      caf_ref->next = nullptr;
      move_coarray_ref (&(*post_caf_ref_expr)->ref, expr);
      fixup_comp_refs (expr);

      if (caf_ref && caf_ref->u.ar.dimen)
	{
	  base->as = gfc_copy_array_spec (caf_ref->u.ar.as);
	  base->as->corank = 0;
	  base->attr.dimension = 1;
	  base->attr.allocatable = caf_ref->u.ar.as->type != AS_EXPLICIT;
	}
      base->ts = *caf_ts;
    }
  (*post_caf_ref_expr)->ts = expr->ts;
  if (base->ts.type == BT_CHARACTER)
    {
      base->ts.u.cl = gfc_get_charlen ();
      *base->ts.u.cl = *(caf_ts->u.cl);
      base->ts.deferred = 1;
      base->ts.u.cl->length = nullptr;
    }
  else if (base->ts.type == BT_DERIVED)
    remove_coarray_from_derived_type (base, ns);
  else if (base->ts.type == BT_CLASS)
    convert_coarray_class_to_derived_type (base, ns);

  gfc_expression_rank (*post_caf_ref_expr);
  if (for_send)
    gfc_expression_rank (expr);
  else
    expr->rank = (*post_caf_ref_expr)->rank;
}

static void add_caf_get_from_remote (gfc_expr *e);

static gfc_component *
find_comp (gfc_symbol *type, gfc_expr *e, int *cnt, const bool is_var)
{
  char *temp_name = nullptr;
  gfc_component *comp = type->components;

  /* For variables:
     - look up same name or create new
     all else:
     - create unique new
  */
  if (is_var)
    {
      ++(*cnt);
      free (temp_name);
      temp_name = xasprintf ("caf_temp_%s_%d", e->symtree->name, *cnt);
      while (comp && strcmp (comp->name, temp_name) != 0)
	comp = comp->next;
      if (!comp)
	{
	  const bool added = gfc_add_component (type, temp_name, &comp);
	  gcc_assert (added);
	}
    }
  else
    {
      int r = -1;
      /* Components are always appended, i.e., when searching to add a unique
	 one just iterating forward is sufficient.  */
      do
	{
	  ++(*cnt);
	  free (temp_name);
	  temp_name = xasprintf ("caf_temp_%s_%d", e->symtree->name, *cnt);

	  while (comp && (r = strcmp (comp->name, temp_name)) <= 0)
	    comp = comp->next;
	}
      while (comp && r <= 0);
      {
	const bool added = gfc_add_component (type, temp_name, &comp);
	gcc_assert (added);
      }
    }

  comp->loc = e->where;
  comp->ts = e->ts;
  free (temp_name);

  return comp;
}

static void
check_add_new_comp_handle_array (gfc_expr *e, gfc_symbol *type,
				 gfc_symbol *add_data)
{
  gfc_component *comp;
  int cnt = -1;
  gfc_symtree *caller_image;
  gfc_code *pre_code = caf_accessor_prepend;
  bool static_array_or_scalar = true;
  symbol_attribute e_attr = gfc_expr_attr (e);

  gfc_free_shape (&e->shape, e->rank);

  /* When already code to prepend into the accessor exists, go to
     the end of the chain.  */
  for (; pre_code && pre_code->next; pre_code = pre_code->next)
    ;

  comp = find_comp (type, e, &cnt,
		    e->symtree->n.sym->attr.flavor == FL_VARIABLE
		      || e->symtree->n.sym->attr.flavor == FL_PARAMETER);

  if (e->expr_type == EXPR_FUNCTION
      || (e->expr_type == EXPR_VARIABLE && e_attr.dimension
	  && e_attr.allocatable))
    {
      gfc_code *code;
      gfc_symtree *st;
      const bool created
	= !gfc_get_sym_tree (comp->name, gfc_current_ns, &st, false, &e->where);
      gcc_assert (created);

      st->n.sym->ts = e->ts;
      gfc_set_sym_referenced (st->n.sym);
      code = gfc_get_code (EXEC_ASSIGN);
      code->loc = e->where;
      code->expr1 = gfc_get_variable_expr (st);
      code->expr2 = XCNEW (gfc_expr);
      *(code->expr2) = *e;
      code->next = *current_code;
      *current_code = code;

      if (e_attr.dimension)
	{
	  gfc_array_spec *as = get_arrayspec_from_expr (e);
	  static_array_or_scalar = gfc_is_compile_time_shape (as);

	  comp->attr.dimension = 1;
	  st->n.sym->attr.dimension = 1;
	  st->n.sym->as = as;

	  if (!static_array_or_scalar)
	    {
	      comp->attr.allocatable = 1;
	      st->n.sym->attr.allocatable = 1;
	    }
	  code->expr1->rank = as->rank;
	  gfc_add_full_array_ref (code->expr1, gfc_copy_array_spec (as));
	  comp->as = gfc_copy_array_spec (as);
	}

      gfc_expression_rank (code->expr1);
      comp->initializer = gfc_get_variable_expr (st);
      gfc_commit_symbol (st->n.sym);
    }
  else
    {
      comp->initializer = gfc_copy_expr (e);
      if (e_attr.dimension)
	{
	  comp->attr.dimension = 1;
	  comp->as = get_arrayspec_from_expr (e);
	}
    }
  comp->initializer->where = e->where;
  comp->attr.access = ACCESS_PRIVATE;
  memset (e, 0, sizeof (gfc_expr));
  e->ts = comp->initializer->ts;
  e->expr_type = EXPR_VARIABLE;
  e->where = comp->initializer->where;

  if (comp->as && comp->as->rank)
    {
      if (static_array_or_scalar)
	{
	  e->ref = gfc_get_ref ();
	  e->ref->type = REF_ARRAY;
	  e->ref->u.ar.as = gfc_copy_array_spec (add_data->as);
	  e->ref->u.ar.codimen = 1;
	  e->ref->u.ar.dimen_type[0] = DIMEN_THIS_IMAGE;
	}
      else
	{
	  gfc_code *c;
	  gfc_symtree *lv, *ad;
	  bool created = !gfc_get_sym_tree (comp->name, add_data->ns, &lv,
					    false, &e->where);
	  gcc_assert (created);

	  lv->n.sym->ts = e->ts;
	  lv->n.sym->attr.dimension = 1;
	  lv->n.sym->attr.allocatable = 1;
	  lv->n.sym->attr.flavor = FL_VARIABLE;
	  lv->n.sym->as = gfc_copy_array_spec (comp->as);
	  gfc_set_sym_referenced (lv->n.sym);
	  gfc_commit_symbol (lv->n.sym);
	  c = gfc_get_code (EXEC_ASSIGN);
	  c->loc = e->where;
	  c->expr1 = gfc_get_variable_expr (lv);
	  c->expr1->where = e->where;

	  created = !gfc_find_sym_tree (add_data->name, add_data->ns, 0, &ad);
	  gcc_assert (created);
	  c->expr2 = gfc_get_variable_expr (ad);
	  c->expr2->where = e->where;
	  c->expr2->ts = comp->initializer->ts;
	  c->expr2->ref = gfc_get_ref ();
	  c->expr2->ref->type = REF_ARRAY;
	  c->expr2->ref->u.ar.as = gfc_copy_array_spec (add_data->as);
	  c->expr2->ref->u.ar.codimen = 1;
	  c->expr2->ref->u.ar.dimen_type[0] = DIMEN_ELEMENT;
	  caller_image
	    = gfc_find_symtree_in_proc ("caller_image", add_data->ns);
	  gcc_assert (caller_image);
	  c->expr2->ref->u.ar.start[0] = gfc_get_variable_expr (caller_image);
	  c->expr2->ref->u.ar.start[0]->where = e->where;
	  created = gfc_find_component (ad->n.sym->ts.u.derived, comp->name,
					false, true, &c->expr2->ref->next)
		    != nullptr;
	  gcc_assert (created);
	  c->expr2->rank = comp->as->rank;
	  gfc_add_full_array_ref (c->expr2, gfc_copy_array_spec (comp->as));
	  gfc_set_sym_referenced (ad->n.sym);
	  gfc_commit_symbol (ad->n.sym);
	  if (pre_code)
	    pre_code->next = c;
	  else
	    caf_accessor_prepend = c;
	  add_caf_get_from_remote (c->expr2);

	  e->symtree = lv;
	  gfc_expression_rank (e);
	  gfc_add_full_array_ref (e, gfc_copy_array_spec (comp->as));
	}
    }
  else
    {
      e->ref = gfc_get_ref ();
      e->ref->type = REF_ARRAY;
      e->ref->u.ar.as = gfc_copy_array_spec (add_data->as);
      e->ref->u.ar.codimen = 1;
      e->ref->u.ar.dimen_type[0] = DIMEN_THIS_IMAGE;
    }

  if (static_array_or_scalar)
    {
      const bool created
	= gfc_find_component (add_data->ts.u.derived, comp->name, false, true,
			      &e->ref);
      gcc_assert (created);
      e->symtree = gfc_find_symtree (add_data->ns->sym_root, add_data->name);
      gcc_assert (e->symtree);
      if (IS_CLASS_ARRAY (e->ref->u.c.component)
	  || e->ref->u.c.component->attr.dimension)
	{
	  gfc_add_full_array_ref (e, e->ref->u.c.component->ts.type == BT_CLASS
				       ? CLASS_DATA (e->ref->u.c.component)->as
				       : e->ref->u.c.component->as);
	  e->ref->next->u.ar.dimen
	    = e->ref->u.c.component->ts.type == BT_CLASS
		? CLASS_DATA (e->ref->u.c.component)->as->rank
		: e->ref->u.c.component->as->rank;
	}
      gfc_expression_rank (e);
    }
}

static void
check_add_new_component (gfc_symbol *type, gfc_expr *e, gfc_symbol *add_data)
{
  if (e)
    {
      switch (e->expr_type)
	{
	case EXPR_CONSTANT:
	case EXPR_NULL:
	  break;
	case EXPR_OP:
	  check_add_new_component (type, e->value.op.op1, add_data);
	  if (e->value.op.op2)
	    check_add_new_component (type, e->value.op.op2, add_data);
	  break;
	case EXPR_COMPCALL:
	  for (gfc_actual_arglist *actual = e->value.compcall.actual; actual;
	       actual = actual->next)
	    check_add_new_component (type, actual->expr, add_data);
	  break;
	case EXPR_FUNCTION:
	  if (!e->symtree->n.sym->attr.pure
	      && !e->symtree->n.sym->attr.elemental)
	    /* Treat non-pure/non-elemental functions.  */
	    check_add_new_comp_handle_array (e, type, add_data);
	  else
	    for (gfc_actual_arglist *actual = e->value.function.actual; actual;
		 actual = actual->next)
	      check_add_new_component (type, actual->expr, add_data);
	  break;
	case EXPR_VARIABLE:
	    check_add_new_comp_handle_array (e, type, add_data);
	    break;
	case EXPR_ARRAY:
	case EXPR_PPC:
	case EXPR_STRUCTURE:
	case EXPR_SUBSTRING:
	  gcc_unreachable ();
	default:;
	}
    }
}

static gfc_symbol *
create_caf_add_data_parameter_type (gfc_expr *expr, gfc_namespace *ns,
				    gfc_symbol *add_data)
{
  static int type_cnt = 0;
  char tname[GFC_MAX_SYMBOL_LEN + 1];
  char *name;
  gfc_symbol *type;

  gcc_assert (expr->expr_type == EXPR_VARIABLE);

  strcpy (tname, expr->symtree->name);
  name = xasprintf ("@_caf_add_data_t_%s_%d", tname, ++type_cnt);
  gfc_get_symbol (name, ns, &type);

  type->attr.flavor = FL_DERIVED;
  add_data->ts.u.derived = type;
  add_data->attr.codimension = 1;
  add_data->as = gfc_get_array_spec ();
  add_data->as->corank = 1;
  add_data->as->type = AS_EXPLICIT;
  add_data->as->cotype = AS_DEFERRED;
  add_data->as->lower[0]
    = gfc_get_constant_expr (BT_INTEGER, gfc_default_integer_kind,
			     &expr->where);
  mpz_init (add_data->as->lower[0]->value.integer);
  mpz_set_si (add_data->as->lower[0]->value.integer, 1);

  for (gfc_ref *ref = expr->ref; ref; ref = ref->next)
    {
      if (ref->type == REF_ARRAY)
	{
	  gfc_array_ref *ar = &ref->u.ar;
	  for (int i = 0; i < ar->dimen; ++i)
	    {
	      check_add_new_component (type, ar->start[i], add_data);
	      check_add_new_component (type, ar->end[i], add_data);
	      check_add_new_component (type, ar->stride[i], add_data);
	    }
	}
    }

  type->declared_at = expr->where;
  gfc_set_sym_referenced (type);
  gfc_commit_symbol (type);
  return type;
}

static void
remove_caf_ref (gfc_expr *expr, const bool conv_to_this_image_cafref = false)
{
  gfc_ref *ref = expr->ref;
  while (ref && (ref->type != REF_ARRAY || ref->u.ar.codimen == 0))
    {
      ref = ref->next;
    }
  if (ref && ref->type == REF_ARRAY && ref->u.ar.codimen != 0)
    {
      if (ref->u.ar.dimen != 0)
	{
	  ref->u.ar.codimen = 0;
	  ref = ref->next;
	}
      else
	{
	  if (conv_to_this_image_cafref)
	    {
	      for (int i = ref->u.ar.dimen;
		   i < ref->u.ar.dimen + ref->u.ar.codimen; ++i)
		ref->u.ar.dimen_type[i] = DIMEN_THIS_IMAGE;
	    }
	  else
	    {
	      expr->ref = ref->next;
	      ref->next = NULL;
	      gfc_free_ref_list (ref);
	      ref = expr->ref;
	    }
	}
    }
  fixup_comp_refs (expr);
}

static gfc_expr *
create_get_callback (gfc_expr *expr)
{
  gfc_namespace *ns;
  gfc_symbol *extproc, *proc, *buffer, *free_buffer, *base, *get_data,
    *old_buffer_data, *caller_image;
  char tname[GFC_MAX_SYMBOL_LEN + 1];
  char *name;
  const char *mname;
  gfc_expr *cb, *post_caf_ref_expr;
  gfc_code *code;
  int expr_rank = expr->rank;
  gfc_code *backup_caf_accessor_prepend = caf_accessor_prepend;
  caf_accessor_prepend = nullptr;

  /* Find the top-level namespace.  */
  for (ns = gfc_current_ns; ns->parent; ns = ns->parent)
    ;

  if (expr->expr_type == EXPR_VARIABLE)
    strcpy (tname, expr->symtree->name);
  else
    strcpy (tname, "dummy");
  if (expr->symtree->n.sym->module)
    mname = expr->symtree->n.sym->module;
  else
    mname = "main";
  name = xasprintf ("_caf_accessor_%s_%s_%d", mname, tname, ++caf_sym_cnt);
  gfc_get_symbol (name, ns, &extproc);
  extproc->declared_at = expr->where;
  gfc_set_sym_referenced (extproc);
  ++extproc->refs;
  gfc_commit_symbol (extproc);

  /* Set up namespace.  */
  gfc_namespace *sub_ns = gfc_get_namespace (ns, 0);
  sub_ns->sibling = ns->contained;
  ns->contained = sub_ns;
  sub_ns->resolved = 1;
  /* Set up procedure symbol.  */
  gfc_find_symbol (name, sub_ns, 1, &proc);
  sub_ns->proc_name = proc;
  proc->attr.if_source = IFSRC_DECL;
  proc->attr.access = ACCESS_PUBLIC;
  gfc_add_subroutine (&proc->attr, name, NULL);
  proc->attr.host_assoc = 1;
  proc->attr.always_explicit = 1;
  ++proc->refs;
  proc->declared_at = expr->where;
  gfc_commit_symbol (proc);
  free (name);

  split_expr_at_caf_ref (expr, sub_ns, &post_caf_ref_expr, false);

  if (ns->proc_name->attr.flavor == FL_MODULE)
    proc->module = ns->proc_name->name;
  gfc_set_sym_referenced (proc);
  /* Set up formal arguments.  */
  gfc_formal_arglist **argptr = &proc->formal;
#define ADD_ARG(name, nsym, stype, skind, sintent)                             \
  gfc_get_symbol (name, sub_ns, &nsym);                                        \
  nsym->ts.type = stype;                                                       \
  nsym->ts.kind = skind;                                                       \
  nsym->attr.flavor = FL_PARAMETER;                                            \
  nsym->attr.dummy = 1;                                                        \
  nsym->attr.intent = sintent;                                                 \
  nsym->declared_at = expr->where;                                             \
  gfc_set_sym_referenced (nsym);                                               \
  *argptr = gfc_get_formal_arglist ();                                         \
  (*argptr)->sym = nsym;                                                       \
  argptr = &(*argptr)->next

  name = xasprintf ("add_data_%s_%s_%d", mname, tname, caf_sym_cnt);
  ADD_ARG (name, get_data, BT_DERIVED, 0, INTENT_IN);
  gfc_commit_symbol (get_data);
  free (name);

  ADD_ARG ("caller_image", caller_image, BT_INTEGER, gfc_default_integer_kind,
	   INTENT_IN);
  gfc_commit_symbol (caller_image);

  ADD_ARG ("buffer", buffer, expr->ts.type, expr->ts.kind, INTENT_INOUT);
  buffer->ts = expr->ts;
  if (expr_rank)
    {
      buffer->as = gfc_get_array_spec ();
      buffer->as->rank = expr_rank;
      if (expr->shape)
	{
	  buffer->as->type = AS_EXPLICIT;
	  for (int d = 0; d < expr_rank; ++d)
	    {
	      buffer->as->lower[d]
		= gfc_get_constant_expr (BT_INTEGER, gfc_index_integer_kind,
					 &gfc_current_locus);
	      gfc_mpz_set_hwi (buffer->as->lower[d]->value.integer, 1);
	      buffer->as->upper[d]
		= gfc_get_constant_expr (BT_INTEGER, gfc_index_integer_kind,
					 &gfc_current_locus);
	      gfc_mpz_set_hwi (buffer->as->upper[d]->value.integer,
			       gfc_mpz_get_hwi (expr->shape[d]));
	    }
	  buffer->attr.allocatable = 1;
	}
      else
	{
	  buffer->as->type = AS_DEFERRED;
	  buffer->attr.allocatable = 1;
	}
      buffer->attr.dimension = 1;
    }
  else
    buffer->attr.pointer = 1;
  if (buffer->ts.type == BT_CHARACTER)
    {
      buffer->ts.u.cl = gfc_get_charlen ();
      *buffer->ts.u.cl = *expr->ts.u.cl;
      buffer->ts.u.cl->length = gfc_copy_expr (expr->ts.u.cl->length);
    }
  gfc_commit_symbol (buffer);

  ADD_ARG ("free_buffer", free_buffer, BT_LOGICAL, gfc_default_logical_kind,
	   INTENT_OUT);
  gfc_commit_symbol (free_buffer);

  // ADD_ARG (expr->symtree->name, base, BT_VOID, INTENT_IN);
  base = post_caf_ref_expr->symtree->n.sym;
  gfc_set_sym_referenced (base);
  gfc_commit_symbol (base);
  *argptr = gfc_get_formal_arglist ();
  (*argptr)->sym = base;
  argptr = &(*argptr)->next;
  gfc_commit_symbol (base);
#undef ADD_ARG

  /* Set up code.  */
  if (expr->rank != 0)
    {
      /* Code: old_buffer_ptr = C_LOC (buffer);  */
      code = sub_ns->code = gfc_get_code (EXEC_ASSIGN);
      gfc_get_symbol ("old_buffer_data", sub_ns, &old_buffer_data);
      old_buffer_data->ts.type = BT_VOID;
      old_buffer_data->attr.flavor = FL_VARIABLE;
      old_buffer_data->declared_at = expr->where;
      gfc_set_sym_referenced (old_buffer_data);
      gfc_commit_symbol (old_buffer_data);
      code->loc = expr->where;
      code->expr1 = gfc_lval_expr_from_sym (old_buffer_data);
      code->expr2 = gfc_build_intrinsic_call (ns, GFC_ISYM_C_LOC, "C_LOC",
					      gfc_current_locus, 1,
					      gfc_lval_expr_from_sym (buffer));
      code->next = gfc_get_code (EXEC_ASSIGN);
      code = code->next;
    }
  else
    code = sub_ns->code = gfc_get_code (EXEC_POINTER_ASSIGN);

  /* Code: buffer = expr;  */
  code->loc = expr->where;
  code->expr1 = gfc_lval_expr_from_sym (buffer);
  code->expr2 = post_caf_ref_expr;
  remove_caf_ref (post_caf_ref_expr);
  get_data->ts.u.derived
    = create_caf_add_data_parameter_type (code->expr2, ns, get_data);
  if (code->expr2->rank == 0 && code->expr2->ts.type != BT_CHARACTER)
    code->expr2 = gfc_build_intrinsic_call (ns, GFC_ISYM_C_LOC, "C_LOC",
					    gfc_current_locus, 1, code->expr2);

  /* Code: *free_buffer = old_buffer_ptr /= C_LOC (buffer); for rank != 0 or
   *       *free_buffer = 0; for rank == 0.  */
  code->next = gfc_get_code (EXEC_ASSIGN);
  code = code->next;
  code->loc = expr->where;
  code->expr1 = gfc_lval_expr_from_sym (free_buffer);
  if (expr->rank != 0)
    {
      code->expr2 = gfc_get_operator_expr (
	&gfc_current_locus, INTRINSIC_NE_OS,
	gfc_lval_expr_from_sym (old_buffer_data),
	gfc_build_intrinsic_call (ns, GFC_ISYM_C_LOC, "C_LOC",
				  gfc_current_locus, 1,
				  gfc_lval_expr_from_sym (buffer)));
      code->expr2->ts.type = BT_LOGICAL;
      code->expr2->ts.kind = gfc_default_logical_kind;
    }
  else
    {
      code->expr2 = gfc_get_logical_expr (gfc_default_logical_kind,
					  &gfc_current_locus, false);
    }

  cb = gfc_lval_expr_from_sym (extproc);
  cb->ts.interface = extproc;

  if (caf_accessor_prepend)
    {
      gfc_code *c = caf_accessor_prepend;
      /* Find last in chain.  */
      for (; c->next; c = c->next)
	;
      c->next = sub_ns->code;
      sub_ns->code = caf_accessor_prepend;
    }
  caf_accessor_prepend = backup_caf_accessor_prepend;
  return cb;
}

void
add_caf_get_from_remote (gfc_expr *e)
{
  gfc_expr *wrapper, *tmp_expr, *get_from_remote_expr,
    *get_from_remote_hash_expr;
  gfc_ref *ref;
  int n;

  for (ref = e->ref; ref; ref = ref->next)
    if (ref->type == REF_ARRAY && ref->u.ar.codimen > 0)
      break;
  if (ref == NULL)
    return;

  for (n = ref->u.ar.dimen; n < ref->u.ar.dimen + ref->u.ar.codimen; n++)
    if (ref->u.ar.dimen_type[n] != DIMEN_ELEMENT)
      return;

  tmp_expr = XCNEW (gfc_expr);
  *tmp_expr = *e;
  get_from_remote_expr = create_get_callback (tmp_expr);
  get_from_remote_hash_expr = gfc_get_expr ();
  get_from_remote_hash_expr->expr_type = EXPR_CONSTANT;
  get_from_remote_hash_expr->ts.type = BT_INTEGER;
  get_from_remote_hash_expr->ts.kind = gfc_default_integer_kind;
  get_from_remote_hash_expr->where = tmp_expr->where;
  mpz_init_set_ui (get_from_remote_hash_expr->value.integer,
		   gfc_hash_value (get_from_remote_expr->symtree->n.sym));
  wrapper = gfc_build_intrinsic_call (gfc_current_ns, GFC_ISYM_CAF_GET,
				      "caf_get", tmp_expr->where, 3, tmp_expr,
				      get_from_remote_hash_expr,
				      get_from_remote_expr);
  gfc_add_caf_accessor (get_from_remote_hash_expr, get_from_remote_expr);
  wrapper->ts = e->ts;
  wrapper->rank = e->rank;
  wrapper->corank = e->corank;
  if (e->rank)
    wrapper->shape = gfc_copy_shape (e->shape, e->rank);
  *e = *wrapper;
  free (wrapper);
}

static gfc_expr *
create_allocated_callback (gfc_expr *expr)
{
  gfc_namespace *ns;
  gfc_symbol *extproc, *proc, *result, *base, *add_data, *caller_image;
  char tname[GFC_MAX_SYMBOL_LEN + 1];
  char *name;
  const char *mname;
  gfc_expr *cb, *post_caf_ref_expr;
  gfc_code *code;
  gfc_code *backup_caf_accessor_prepend = caf_accessor_prepend;
  caf_accessor_prepend = nullptr;
  gfc_expr swp;

  /* Find the top-level namespace.  */
  for (ns = gfc_current_ns; ns->parent; ns = ns->parent)
    ;

  if (expr->value.function.actual->expr->expr_type == EXPR_VARIABLE)
    strcpy (tname, expr->value.function.actual->expr->symtree->name);
  else
    strcpy (tname, "dummy");
  if (expr->value.function.actual->expr->symtree->n.sym->module)
    mname = expr->value.function.actual->expr->symtree->n.sym->module;
  else
    mname = "main";
  name = xasprintf ("_caf_present_%s_%s_%d", mname, tname, ++caf_sym_cnt);
  gfc_get_symbol (name, ns, &extproc);
  extproc->declared_at = expr->where;
  gfc_set_sym_referenced (extproc);
  ++extproc->refs;
  gfc_commit_symbol (extproc);

  /* Set up namespace.  */
  gfc_namespace *sub_ns = gfc_get_namespace (ns, 0);
  sub_ns->sibling = ns->contained;
  ns->contained = sub_ns;
  sub_ns->resolved = 1;
  /* Set up procedure symbol.  */
  gfc_find_symbol (name, sub_ns, 1, &proc);
  sub_ns->proc_name = proc;
  proc->attr.if_source = IFSRC_DECL;
  proc->attr.access = ACCESS_PUBLIC;
  gfc_add_subroutine (&proc->attr, name, NULL);
  proc->attr.host_assoc = 1;
  proc->attr.always_explicit = 1;
  proc->declared_at = expr->where;
  ++proc->refs;
  gfc_commit_symbol (proc);
  free (name);

  split_expr_at_caf_ref (expr->value.function.actual->expr, sub_ns,
			 &post_caf_ref_expr, true);

  if (ns->proc_name->attr.flavor == FL_MODULE)
    proc->module = ns->proc_name->name;
  gfc_set_sym_referenced (proc);
  /* Set up formal arguments.  */
  gfc_formal_arglist **argptr = &proc->formal;
#define ADD_ARG(name, nsym, stype, skind, sintent)                             \
  gfc_get_symbol (name, sub_ns, &nsym);                                        \
  nsym->ts.type = stype;                                                       \
  nsym->ts.kind = skind;                                                       \
  nsym->attr.flavor = FL_PARAMETER;                                            \
  nsym->attr.dummy = 1;                                                        \
  nsym->attr.intent = sintent;                                                 \
  nsym->declared_at = expr->where;                                             \
  gfc_set_sym_referenced (nsym);                                               \
  *argptr = gfc_get_formal_arglist ();                                         \
  (*argptr)->sym = nsym;                                                       \
  argptr = &(*argptr)->next

  name = xasprintf ("add_data_%s_%s_%d", mname, tname, ++caf_sym_cnt);
  ADD_ARG (name, add_data, BT_DERIVED, 0, INTENT_IN);
  gfc_commit_symbol (add_data);
  free (name);
  ADD_ARG ("caller_image", caller_image, BT_INTEGER, gfc_default_integer_kind,
	   INTENT_IN);
  gfc_commit_symbol (caller_image);

  ADD_ARG ("result", result, BT_LOGICAL, gfc_default_logical_kind, INTENT_OUT);
  gfc_commit_symbol (result);

  // ADD_ARG (expr->symtree->name, base, BT_VOID, INTENT_IN);
  base = post_caf_ref_expr->symtree->n.sym;
  gfc_set_sym_referenced (base);
  gfc_commit_symbol (base);
  *argptr = gfc_get_formal_arglist ();
  (*argptr)->sym = base;
  argptr = &(*argptr)->next;
  gfc_commit_symbol (base);
#undef ADD_ARG

  /* Set up code.  */
  /* Code: result = post_caf_ref_expr;  */
  code = sub_ns->code = gfc_get_code (EXEC_ASSIGN);
  code->loc = expr->where;
  code->expr1 = gfc_lval_expr_from_sym (result);
  swp = *expr;
  *expr = *swp.value.function.actual->expr;
  swp.value.function.actual->expr = nullptr;
  code->expr2 = gfc_copy_expr (&swp);
  code->expr2->value.function.actual->expr = post_caf_ref_expr;

  remove_caf_ref (code->expr2->value.function.actual->expr, true);
  add_data->ts.u.derived
    = create_caf_add_data_parameter_type (post_caf_ref_expr, ns, add_data);

  cb = gfc_lval_expr_from_sym (extproc);
  cb->ts.interface = extproc;

  if (caf_accessor_prepend)
    {
      gfc_code *c = caf_accessor_prepend;
      /* Find last in chain.  */
      for (; c->next; c = c->next)
	;
      c->next = sub_ns->code;
      sub_ns->code = caf_accessor_prepend;
    }
  caf_accessor_prepend = backup_caf_accessor_prepend;
  return cb;
}

static void
rewrite_caf_allocated (gfc_expr **e)
{
  gfc_expr *present_fn_expr, *present_hash_expr, *wrapper;

  present_fn_expr = create_allocated_callback (*e);

  present_hash_expr = gfc_get_expr ();
  present_hash_expr->expr_type = EXPR_CONSTANT;
  present_hash_expr->ts.type = BT_INTEGER;
  present_hash_expr->ts.kind = gfc_default_integer_kind;
  present_hash_expr->where = (*e)->where;
  mpz_init_set_ui (present_hash_expr->value.integer,
		   gfc_hash_value (present_fn_expr->symtree->n.sym));
  wrapper
    = gfc_build_intrinsic_call (gfc_current_ns,
				GFC_ISYM_CAF_IS_PRESENT_ON_REMOTE,
				"caf_is_present_on_remote", (*e)->where, 3, *e,
				present_hash_expr, present_fn_expr);
  gfc_add_caf_accessor (present_hash_expr, present_fn_expr);
  *e = wrapper;
}

static gfc_expr *
create_send_callback (gfc_expr *expr, gfc_expr *rhs)
{
  gfc_namespace *ns;
  gfc_symbol *extproc, *proc, *buffer, *base, *send_data, *caller_image;
  char tname[GFC_MAX_SYMBOL_LEN + 1];
  char *name;
  const char *mname;
  gfc_expr *cb, *post_caf_ref_expr;
  gfc_code *code;
  gfc_code *backup_caf_accessor_prepend = caf_accessor_prepend;
  caf_accessor_prepend = nullptr;

  /* Find the top-level namespace.  */
  for (ns = gfc_current_ns; ns->parent; ns = ns->parent)
    ;

  if (expr->expr_type == EXPR_VARIABLE)
    strcpy (tname, expr->symtree->name);
  else
    strcpy (tname, "dummy");
  if (expr->symtree->n.sym->module)
    mname = expr->symtree->n.sym->module;
  else
    mname = "main";
  name = xasprintf ("_caf_accessor_%s_%s_%d", mname, tname, ++caf_sym_cnt);
  gfc_get_symbol (name, ns, &extproc);
  extproc->declared_at = expr->where;
  gfc_set_sym_referenced (extproc);
  ++extproc->refs;
  gfc_commit_symbol (extproc);

  /* Set up namespace.  */
  gfc_namespace *sub_ns = gfc_get_namespace (ns, 0);
  sub_ns->sibling = ns->contained;
  ns->contained = sub_ns;
  sub_ns->resolved = 1;
  /* Set up procedure symbol.  */
  gfc_find_symbol (name, sub_ns, 1, &proc);
  sub_ns->proc_name = proc;
  proc->attr.if_source = IFSRC_DECL;
  proc->attr.access = ACCESS_PUBLIC;
  gfc_add_subroutine (&proc->attr, name, NULL);
  proc->attr.host_assoc = 1;
  proc->attr.always_explicit = 1;
  ++proc->refs;
  proc->declared_at = expr->where;
  gfc_commit_symbol (proc);
  free (name);

  split_expr_at_caf_ref (expr, sub_ns, &post_caf_ref_expr, true);

  if (ns->proc_name->attr.flavor == FL_MODULE)
    proc->module = ns->proc_name->name;
  gfc_set_sym_referenced (proc);
  /* Set up formal arguments.  */
  gfc_formal_arglist **argptr = &proc->formal;
#define ADD_ARG(name, nsym, stype, skind, sintent)                             \
  gfc_get_symbol (name, sub_ns, &nsym);                                        \
  nsym->ts.type = stype;                                                       \
  nsym->ts.kind = skind;                                                       \
  nsym->attr.flavor = FL_PARAMETER;                                            \
  nsym->attr.dummy = 1;                                                        \
  nsym->attr.intent = sintent;                                                 \
  nsym->declared_at = expr->where;                                             \
  gfc_set_sym_referenced (nsym);                                               \
  *argptr = gfc_get_formal_arglist ();                                         \
  (*argptr)->sym = nsym;                                                       \
  argptr = &(*argptr)->next

  name = xasprintf ("add_send_data_%s_%s_%d", mname, tname, caf_sym_cnt);
  ADD_ARG (name, send_data, BT_DERIVED, 0, INTENT_IN);
  gfc_commit_symbol (send_data);
  free (name);

  ADD_ARG ("caller_image", caller_image, BT_INTEGER, gfc_default_integer_kind,
	   INTENT_IN);
  gfc_commit_symbol (caller_image);

  // ADD_ARG (expr->symtree->name, base, BT_VOID, INTENT_IN);
  base = post_caf_ref_expr->symtree->n.sym;
  base->attr.intent = INTENT_INOUT;
  gfc_set_sym_referenced (base);
  gfc_commit_symbol (base);
  *argptr = gfc_get_formal_arglist ();
  (*argptr)->sym = base;
  argptr = &(*argptr)->next;
  gfc_commit_symbol (base);

  ADD_ARG ("buffer", buffer, rhs->ts.type, rhs->ts.kind, INTENT_IN);
  buffer->ts = rhs->ts;
  if (rhs->rank)
    {
      buffer->as = gfc_get_array_spec ();
      buffer->as->rank = rhs->rank;
      buffer->as->type = AS_DEFERRED;
      buffer->attr.allocatable = 1;
      buffer->attr.dimension = 1;
    }
  if (buffer->ts.type == BT_CHARACTER)
    {
      buffer->ts.u.cl = gfc_get_charlen ();
      *buffer->ts.u.cl = *rhs->ts.u.cl;
      buffer->ts.deferred = 1;
      buffer->ts.u.cl->length = gfc_copy_expr (rhs->ts.u.cl->length);
    }
  gfc_commit_symbol (buffer);
#undef ADD_ARG

  /* Set up code.  */
  /* Code: base = buffer;  */
  code = sub_ns->code = gfc_get_code (EXEC_ASSIGN);
  code->loc = expr->where;
  code->expr1 = post_caf_ref_expr;
  if (code->expr1->ts.type == BT_CHARACTER
      && code->expr1->ts.kind != buffer->ts.kind)
    {
      bool converted;
      code->expr2 = gfc_lval_expr_from_sym (buffer);
      converted = gfc_convert_chartype (code->expr2, &code->expr1->ts);
      gcc_assert (converted);
    }
  else if (code->expr1->ts.type != buffer->ts.type)
    {
      bool converted;
      code->expr2 = gfc_lval_expr_from_sym (buffer);
      converted = gfc_convert_type_warn (code->expr2, &code->expr1->ts, 0, 0,
					 buffer->attr.dimension);
      gcc_assert (converted);
    }
  else
    code->expr2 = gfc_lval_expr_from_sym (buffer);
  remove_caf_ref (post_caf_ref_expr);
  send_data->ts.u.derived
    = create_caf_add_data_parameter_type (code->expr1, ns, send_data);

  cb = gfc_lval_expr_from_sym (extproc);
  cb->ts.interface = extproc;

  if (caf_accessor_prepend)
    {
      gfc_code *c = caf_accessor_prepend;
      /* Find last in chain.  */
      for (; c->next; c = c->next)
	;
      c->next = sub_ns->code;
      sub_ns->code = caf_accessor_prepend;
    }
  caf_accessor_prepend = backup_caf_accessor_prepend;
  return cb;
}

static void
rewrite_caf_send (gfc_code *c)
{
  gfc_expr *send_to_remote_expr, *send_to_remote_hash_expr, *lhs, *rhs;
  gfc_actual_arglist *arg = c->ext.actual;

  lhs = arg->expr;
  arg = arg->next;
  rhs = arg->expr;
  /* Detect an already rewritten caf_send.  */
  if (arg->next && arg->next->expr->expr_type == EXPR_CONSTANT
      && arg->next->next && arg->next->next->expr->expr_type == EXPR_VARIABLE)
    return;

  send_to_remote_expr = create_send_callback (lhs, rhs);
  send_to_remote_hash_expr = gfc_get_expr ();
  send_to_remote_hash_expr->expr_type = EXPR_CONSTANT;
  send_to_remote_hash_expr->ts.type = BT_INTEGER;
  send_to_remote_hash_expr->ts.kind = gfc_default_integer_kind;
  send_to_remote_hash_expr->where = lhs->where;
  mpz_init_set_ui (send_to_remote_hash_expr->value.integer,
		   gfc_hash_value (send_to_remote_expr->symtree->n.sym));
  arg->next = gfc_get_actual_arglist ();
  arg = arg->next;
  arg->expr = send_to_remote_hash_expr;
  arg->next = gfc_get_actual_arglist ();
  arg = arg->next;
  arg->expr = send_to_remote_expr;
  gfc_add_caf_accessor (send_to_remote_hash_expr, send_to_remote_expr);

  if (gfc_is_coindexed (rhs))
    {
      gfc_expr *get_from_remote_expr, *get_from_remote_hash_expr;

      c->resolved_isym = gfc_intrinsic_subroutine_by_id (GFC_ISYM_CAF_SENDGET);
      get_from_remote_expr = create_get_callback (rhs);
      get_from_remote_hash_expr = gfc_get_expr ();
      get_from_remote_hash_expr->expr_type = EXPR_CONSTANT;
      get_from_remote_hash_expr->ts.type = BT_INTEGER;
      get_from_remote_hash_expr->ts.kind = gfc_default_integer_kind;
      get_from_remote_hash_expr->where = rhs->where;
      mpz_init_set_ui (get_from_remote_hash_expr->value.integer,
		       gfc_hash_value (get_from_remote_expr->symtree->n.sym));
      arg->next = gfc_get_actual_arglist ();
      arg = arg->next;
      arg->expr = get_from_remote_hash_expr;
      arg->next = gfc_get_actual_arglist ();
      arg = arg->next;
      arg->expr = get_from_remote_expr;
      gfc_add_caf_accessor (get_from_remote_hash_expr, get_from_remote_expr);
    }
}

static int
coindexed_expr_callback (gfc_expr **e, int *walk_subtrees,
			 void *data ATTRIBUTE_UNUSED)
{
  *walk_subtrees = 1;

  switch ((*e)->expr_type)
    {
    case EXPR_VARIABLE:
      if (!caf_on_lhs && gfc_is_coindexed (*e))
	{
	  add_caf_get_from_remote (*e);
	  *walk_subtrees = 0;
	}
      /* Clear the flag to rewrite caf_gets in sub expressions of the lhs.  */
      caf_on_lhs = false;
      break;
    case EXPR_FUNCTION:
      if ((*e)->value.function.isym)
	switch ((*e)->value.function.isym->id)
	  {
	  case GFC_ISYM_ALLOCATED:
	    if ((*e)->value.function.actual->expr
		&& gfc_is_coindexed ((*e)->value.function.actual->expr))
	      {
		rewrite_caf_allocated (e);
		*walk_subtrees = 0;
	      }
	    break;
	  case GFC_ISYM_CAF_GET:
	  case GFC_ISYM_CAF_IS_PRESENT_ON_REMOTE:
	    *walk_subtrees = 0;
	    break;
	  default:
	    break;
	  }
    default:
      break;
    }

  return 0;
}

static int
coindexed_code_callback (gfc_code **c, int *walk_subtrees,
			 void *data ATTRIBUTE_UNUSED)
{
  int ws = 1;
  current_code = c;

  switch ((*c)->op)
    {
    case EXEC_ASSIGN:
    case EXEC_POINTER_ASSIGN:
      caf_on_lhs = true;
      coindexed_expr_callback (&((*c)->expr1), &ws, NULL);
      caf_on_lhs = false;
      ws = 1;
      coindexed_expr_callback (&((*c)->expr2), &ws, NULL);
      *walk_subtrees = ws;
      break;
    case EXEC_LOCK:
    case EXEC_UNLOCK:
    case EXEC_EVENT_POST:
    case EXEC_EVENT_WAIT:
      *walk_subtrees = 0;
      break;
    case EXEC_CALL:
      *walk_subtrees = 1;
      if ((*c)->resolved_isym)
	switch ((*c)->resolved_isym->id)
	  {
	  case GFC_ISYM_CAF_SEND:
	    rewrite_caf_send (*c);
	    *walk_subtrees = 0;
	    break;
	  case GFC_ISYM_CAF_SENDGET:
	    /* Seldomly this routine is called again with the symbol already
	       changed to CAF_SENDGET.  Do not process the subtree again.  The
	       rewrite has already been done by rewrite_caf_send ().  */
	    *walk_subtrees = 0;
	    break;
	  case GFC_ISYM_ATOMIC_ADD:
	  case GFC_ISYM_ATOMIC_AND:
	  case GFC_ISYM_ATOMIC_CAS:
	  case GFC_ISYM_ATOMIC_DEF:
	  case GFC_ISYM_ATOMIC_FETCH_ADD:
	  case GFC_ISYM_ATOMIC_FETCH_AND:
	  case GFC_ISYM_ATOMIC_FETCH_OR:
	  case GFC_ISYM_ATOMIC_FETCH_XOR:
	  case GFC_ISYM_ATOMIC_OR:
	  case GFC_ISYM_ATOMIC_REF:
	  case GFC_ISYM_ATOMIC_XOR:
	    *walk_subtrees = 0;
	    break;
	  default:
	    break;
	  }
      break;
    default:
      *walk_subtrees = 1;
      break;
    }
  return 0;
}

void
gfc_coarray_rewrite (gfc_namespace *ns)
{
  gfc_namespace *saved_ns = gfc_current_ns;
  gfc_current_ns = ns;

  if (flag_coarray == GFC_FCOARRAY_LIB)
    {
      gfc_code_walker (&ns->code, coindexed_code_callback,
		       coindexed_expr_callback, NULL);

      for (gfc_namespace *cns = ns->contained; cns; cns = cns->sibling)
	gfc_coarray_rewrite (cns);
    }

  gfc_current_ns = saved_ns;
}