/*
 * Copyright (c) 1995-2019, NVIDIA CORPORATION.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/** \file
    \brief Fortran utility routines used by Semantic Analyzer to process
           user-defined generics including overloaded operators
 */

#include "gbldefs.h"
#include "global.h"
#include "gramtk.h"
#include "error.h"
#include "symtab.h"
#include "symutl.h"
#include "dtypeutl.h"
#include "semant.h"
#include "scan.h"
#include "semstk.h"
#include "pd.h"
#include "machar.h"
#include "ast.h"
#include "state.h"

static int silent_error_mode = 0;
#undef E155
#define E155(s1, s2)      \
  if (!silent_error_mode) \
  error(155, 3, gbl.lineno, s1, s2)

static int resolve_generic(int, SST *, ITEM *);
static long *args_match(int, int, int, ITEM *, LOGICAL, LOGICAL);
static LOGICAL tkr_match(int, SST *, int, LOGICAL);
static LOGICAL kwd_match(ITEM *, int, char *);
static void get_type_rank(SST *, int *, int *);
static ITEM *make_list(SST *, SST *);
static int resolve_operator(int, SST *, SST *);
static int find_operator(int, SST *, SST *, LOGICAL);
static bool queue_generic_tbp_once(SPTR gnr);
static bool is_conflicted_generic(SPTR, SPTR);

/* macros used by the arg scoring routines */
#define UNIT_SZ 3 /**< bits necessary to hold the max *_MATCH value */
#define NBR_DISTANCE_ELM_BITS ((sizeof(long) * 8 - 1) / UNIT_SZ)
#define DISTANCE_BIT(i) (i % NBR_DISTANCE_ELM_BITS)
#define DISTANCE_ELM(distance, i) (distance[i / NBR_DISTANCE_ELM_BITS])

/* constants returned by by args_match and tkr_match */
#define INF_DISTANCE ((long)-1)
#define MIN_DISTANCE 0
/* also returned by tkr_match */
#define EXACT_MATCH 0
#define EXTND_MATCH 4
/* UNIT_SZ (above) must be the number of bits necessary to hold the max  *_MATCH
 * value */

#define MAN_MAN_MATCH 0
#define MAN_DEV_MATCH 1
#define MAN_HOST_MATCH 2

static int resolved_to_self = 0;

/*
 * Table used to record and return the ST_OPERATOR symbols corresponding
 * to the intrinsic and the assignment operators.
 */
static struct optabstruct {
  int opr;    /* if non-zero, locates the ST_OPERATOR symbol */
  char *name; /* name of the corresponding ST_OPERATOR symbol */
} optab[] = {
    {0, ""},       /* OP_NEG	0 */
    {0, "+"},      /* OP_ADD	1 */
    {0, "-"},      /* OP_SUB	2 */
    {0, "*"},      /* OP_MUL	3 */
    {0, "/"},      /* OP_DIV	4 */
    {0, "**"},     /* OP_XTOI	5 */
    {0, ""},       /* OP_XTOX	6 */
    {0, ""},       /* OP_CMP	7 */
    {0, ""},       /* OP_AIF	8 */
    {0, ""},       /* OP_LD	9 */
    {0, "="},      /* OP_ST	10 */
    {0, ""},       /* OP_FUNC	11 */
    {0, ""},       /* OP_CON	12 */
    {0, "//"},     /* OP_CAT	13 */
    {0, ""},       /* OP_LOG	14 */
    {0, ".eqv."},  /* OP_LEQV	15 */
    {0, ".neqv."}, /* OP_LNEQV	16 */
    {0, ".or."},   /* OP_LOR	17 */
    {0, ".and."},  /* OP_LAND	18 */
    {0, "=="},     /* OP_EQ	19 */
    {0, ">="},     /* OP_GE	20 */
    {0, ">"},      /* OP_GT	21 */
    {0, "<="},     /* OP_LE	22 */
    {0, "<"},      /* OP_LT	23 */
    {0, "!="},     /* OP_NE	24 */
    {0, ".not."},  /* OP_LNOT	25 */
    {0, ""},       /* OP_LOC	26 */
    {0, ""},       /* OP_REF	27 */
    {0, ""},       /* OP_VAL	28 */
};
#define OPTABSIZE 29

/** \brief Determines if we should (re)generate generic type bound procedure
 *  (tbp) bindings based on scope. This should only be done once per scope.
 *
 *  \param gnr is the SPTR of the symbol to check or 0 if N/A.
 *
 *  \return true if we should (re)generate generic tbp bindings, else false.
 */
static bool
queue_generic_tbp_once(SPTR gnr)
{
  if (GNCNTG(gnr) == 0 || gbl.internal > 1) {
    static int generic_tbp_scope = 0;
    bool rslt = (generic_tbp_scope != stb.curr_scope);
    generic_tbp_scope = stb.curr_scope;
    return rslt;
  }
  return false;
}

/** \brief Determines if two generic procedures from different
     modules are conflicted or not. 
 *
 *  \param found_sptrgen is the first generic procedure sptr.
 *  \param func_sptrgen is the second generic procedure sptr.
 *
 *  \return true if the func_sptrgen and found_sptrgen are not conflicted, else
 *   false.
 */
static bool
is_conflicted_generic(SPTR func_sptrgen, SPTR found_sptrgen) {
  return func_sptrgen != found_sptrgen &&
         (PRIVATEG(func_sptrgen) != PRIVATEG(found_sptrgen) ||
         NOT_IN_USEONLYG(func_sptrgen) != NOT_IN_USEONLYG(found_sptrgen));
}

void
check_generic(int gnr)
{
  if (STYPEG(gnr) == ST_USERGENERIC) {
    ;
  } else {
#if DEBUG
    assert(STYPEG(gnr) == ST_OPERATOR, "check_generic, expected ST_OPERATOR",
           STYPEG(gnr), 3);
#endif
  }
}

int
generic_tbp_call(int gnr, SST *stktop, ITEM *list, ITEM *chevlist)
{
  int sptr;
  int dtype;
  int mem;

#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user generic, call %s\n", SYMNAME(gnr));
#endif
  if (queue_generic_tbp_once(gnr)) {
    queue_tbp(0, 0, 0, 0, TBP_COMPLETE_GENERIC);
  }

  if (list == NULL)
    list = ITEM_END;
  sptr = resolve_generic(gnr, stktop, list);
  return sptr;
}

void
generic_call(int gnr, SST *stktop, ITEM *list, ITEM *chevlist)
{
  int sptr;

#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user generic, call %s\n", SYMNAME(gnr));
#endif
  if (list == NULL)
    list = ITEM_END;
  sptr = resolve_generic(gnr, stktop, list);
  if (sptr == 0) {
    SST_ASTP(stktop, 0);
    return;
  }
#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user generic resolved to %s\n", SYMNAME(sptr));
#endif
  SST_SYMP(stktop, -sptr);

    subr_call2(stktop, list, 1);

}

int
generic_tbp_func(int gnr, SST *stktop, ITEM *list)
{
  int sptr;

#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user generic %s\n", SYMNAME(gnr));
#endif

  if (queue_generic_tbp_once(gnr)) {
    queue_tbp(0, 0, 0, 0, TBP_COMPLETE_GENERIC);
  }

  if (list == NULL)
    list = ITEM_END;
  sptr = resolve_generic(gnr, stktop, list);
  return sptr;
}

int
generic_func(int gnr, SST *stktop, ITEM *list)
{
  int sptr;

#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user generic %s\n", SYMNAME(gnr));
#endif
  if (list == NULL)
    list = ITEM_END;
  sptr = resolve_generic(gnr, stktop, list);
  if (sptr == 0) {
    SST_IDP(stktop, S_CONST);
    SST_DTYPEP(stktop, DT_INT);
    return 3;
  }
  if (sptr == -1) {
    /*  the generic resolve to a structure constructor  */
    return 1;
  }
#if DEBUG
  if (DBGBIT(3, 256)) {
    fprintf(gbl.dbgfil, "user generic resolved to %s\n", SYMNAME(sptr));
    if (sptr < stb.firstosym)
      fprintf(gbl.dbgfil, "USING intrinsic generic\n");
  }
#endif
  mkident(stktop);
  SST_SYMP(stktop, sptr);
  SST_DTYPEP(stktop, DTYPEG(sptr));
  if (sptr < stb.firstosym) {
    if (STYPEG(sptr) == ST_PD)
      return ref_pd(stktop, list);
    return ref_intrin(stktop, list);
  }
  SST_ASTP(stktop, mk_id(sptr));
  return func_call2(stktop, list, 1);
}

static long *
set_distance_to(long value, long *distance, int sz)
{
  int i;

  for (i = 0; i < sz; i++)
    distance[i] = value;

  return distance;
}

/* compare distance, distance; return
 * -1 if distance1 < distance2
 *  0 if distance1 == distance2
 *  1 if distance1 > distance2
 */
static int
cmp_arg_score(long *distance1, long *distance2, int sz)
{
  int i;
  if (*distance1 != INF_DISTANCE && *distance2 == INF_DISTANCE) {
    return -1;
  } else if (*distance1 == INF_DISTANCE && *distance2 != INF_DISTANCE) {
    return 1;
  } else if (*distance1 == INF_DISTANCE && *distance2 == INF_DISTANCE) {
    return 0;
  }

  for (i = 0; i < sz; ++i) {
    if (distance1[i] < distance2[i])
      return -1;
    else if (distance1[i] > distance2[i])
      return 1;
  }
  return 0;
}

static int
find_best_generic(int gnr, ITEM *list, int arg_cnt, int try_device,
                  LOGICAL chk_elementals)
{
  int gndsc, nmptr;
  int sptr;
  int sptrgen;
  int found;
  int bind;
  int found_bind;
  int func;
  long *argdistance;
  long *min_argdistance = 0;
  int distance_sz;
  LOGICAL gnr_in_active_scope;
  int dscptr;
  int paramct, curr_paramct;
  SPTR found_sptrgen, func_sptrgen;

  /* find the generic's max nbr of formal args and use it to compute
   * the size of the arg distatnce data item.
   */
  paramct = 0;
  for (sptr = first_hash(gnr); sptr > NOSYM; sptr = HASHLKG(sptr)) {
    sptrgen = sptr;
    while (STYPEG(sptrgen) == ST_ALIAS)
      sptrgen = SYMLKG(sptrgen);
    for (gndsc = GNDSCG(sptrgen); gndsc; gndsc = SYMI_NEXT(gndsc)) {
      func = SYMI_SPTR(gndsc);
      while (STYPEG(func) == ST_MODPROC || STYPEG(func) == ST_ALIAS) {
        /* Need to get the actual routine symbol in order to
         * access the arguments and number of arguments of the routine.
         */
        func = SYMLKG(func);
      }
      dscptr = DPDSCG(func);
      curr_paramct = PARAMCTG(func);
      if (curr_paramct > paramct) {
        paramct = curr_paramct;
      }
    }
  }
  /* initialize arg distance data item */
  distance_sz = paramct / NBR_DISTANCE_ELM_BITS + 1;
  NEW(min_argdistance, long, distance_sz);
  (void)set_distance_to(INF_DISTANCE, min_argdistance, distance_sz);

  nmptr = NMPTRG(gnr);

  found = 0;
  found_bind = 0;
  for (sptr = first_hash(gnr); sptr > NOSYM; sptr = HASHLKG(sptr)) {
    gnr_in_active_scope = FALSE;
    sptrgen = sptr;
    if (NMPTRG(sptrgen) != nmptr)
      continue;
    if (PRIVATEG(sptr) && gbl.currmod && SCOPEG(sptr) != gbl.currmod)
      continue;
    while (STYPEG(sptrgen) == ST_ALIAS)
      sptrgen = SYMLKG(sptrgen);
    if (STYPEG(sptrgen) != ST_USERGENERIC)
      continue;
    /* is the original symbol (sptr, not sptrgen) in an active scope */
    if (test_scope(sptr) >= 0 ||
        (STYPEG(SCOPEG(sptr)) == ST_MODULE && !PRIVATEG(SCOPEG(sptr)))) {
      gnr_in_active_scope = TRUE;
    }
    if (!gnr_in_active_scope && !CLASSG(sptrgen))
      continue;
    if (GNCNTG(sptrgen) == 0 && GTYPEG(sptrgen)) {
      continue; /* Could be an overloaded type */
    }
    if (queue_generic_tbp_once(sptrgen)) {
      queue_tbp(0, 0, 0, 0, TBP_COMPLETE_GENERIC);
    }
    if (GNCNTG(sptrgen) == 0 && !IS_TBP(sptrgen)) {
      /* Ignore if generic tbp overloads sptrgen. This might be
       * an overloaded intrinsic. We check for an overloaded intrinsic
       * below.
       */

      E155("Empty generic procedure -", SYMNAME(sptr));
    }

    for (gndsc = GNDSCG(sptrgen); gndsc; gndsc = SYMI_NEXT(gndsc)) {
      func = SYMI_SPTR(gndsc);
      func_sptrgen = sptrgen;
      if (IS_TBP(func)) {
        /* For generic type bound procedures, use the implementation
         * of the generic bind name for the argument comparison.
         */
        int mem, dty;
        bind = func;
        dty = TBPLNKG(func /*sptrgen*/);
        func = get_implementation(dty, func, 0, &mem);
        if (STYPEG(BINDG(mem)) == ST_OPERATOR ||
            STYPEG(BINDG(mem)) == ST_USERGENERIC) {
          mem = get_specific_member(dty, func);
          func = VTABLEG(mem);
          bind = BINDG(mem);
        }
        if (!func)
          continue;
        mem = get_generic_member(dty, bind);
        if (NOPASSG(mem) && generic_tbp_has_pass_and_nopass(dty, BINDG(mem)))
          continue;
        if (mem && PRIVATEG(mem) && SCOPEG(stb.curr_scope) != SCOPEG(mem))
          continue;
      } else
        bind = 0;
      if (STYPEG(func) == ST_MODPROC) {
        func = SYMLKG(func);
        if (func == 0)
          continue;
      }
      if (STYPEG(func) == ST_ALIAS)
        func = SYMLKG(func);
      if (chk_elementals && ELEMENTALG(func)) {
        argdistance =
            args_match(func, arg_cnt, distance_sz, list, TRUE, try_device == 1);
      } else {
        argdistance = args_match(func, arg_cnt, distance_sz, list, FALSE,
                                 try_device == 1);
      }
      if (found && func && found != func && *min_argdistance != INF_DISTANCE &&
          !is_conflicted_generic(func_sptrgen, found_sptrgen) &&
          cmp_arg_score(argdistance, min_argdistance, distance_sz) == 0) {
        int len;
        char *name, *name_cpy;
        len = strlen(SYMNAME(gnr)) + 1;
        name_cpy = getitem(0, len);
        strcpy(name_cpy, SYMNAME(gnr));
        name = strchr(name_cpy, '$');
        if (name)
          *name = '\0';
        E155("Ambiguous interfaces for generic procedure", name_cpy);
        FREE(argdistance);
        break;
      } else if (cmp_arg_score(argdistance, min_argdistance, distance_sz) ==
                 -1) {
        FREE(min_argdistance);
        min_argdistance = argdistance;
        found = func;
        found_bind = bind;
        found_sptrgen = sptrgen;
      } else {
        FREE(argdistance);
      }
    }
  }
  FREE(min_argdistance);
  found = (found_bind) ? found_bind : found;
  return found;
}

/*
 * Possible return values:
 * -1  : generic resolves to a struct constructor
 *  0  : error
 * >0  : sptr of the 'specific'
 */
static int
resolve_generic(int gnr, SST *stktop, ITEM *list)
{
  int nmptr;
  int arg_cnt;
  ITEM *itemp;
  SST *sp;
  int sptr;
  int found;
  int try_device = 0;

  arg_cnt = 0;
  for (itemp = list; itemp != ITEM_END; itemp = itemp->next) {
    arg_cnt++;
    sp = itemp->t.stkp;
    if (SST_IDG(sp) == S_TRIPLE) {
      /* form is e1:e2:e3 */
      error(76, 3, gbl.lineno, SYMNAME(gnr), CNULL);
      return 0;
    }
    if (SST_IDG(sp) == S_ACONST) {
      mkexpr(sp);
    }
  }
#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "resolve_generic: %s, count %d\n", SYMNAME(gnr),
            arg_cnt);
#endif

  nmptr = NMPTRG(gnr);
/* search HASH list for all user generics of the same name */
  {
    if ((found = find_best_generic(gnr, list, arg_cnt, try_device, FALSE))) {
      return found;
    }
  }

  if ((found = find_best_generic(gnr, list, arg_cnt, 0, TRUE))) {
    return found;
  }

  /* search HASH list for intrinsic generic of the same name */
  for (sptr = gnr; sptr; sptr = HASHLKG(sptr)) {
    if (NMPTRG(sptr) == nmptr && IS_INTRINSIC(STYPEG(sptr)) &&
        sptr < stb.firstosym) {
      return sptr;
    }
  }
  if (STYPEG(gnr) == ST_ENTRY || STYPEG(gnr) == ST_PROC) {
    /* allow specific name to be used also */
    return gnr;
  }
  if (CLASSG(gnr)) {
    char *name_cpy, *name;
    name_cpy = getitem(0, strlen(SYMNAME(gnr)) + 1);
    strcpy(name_cpy, SYMNAME(gnr));
    name = strchr(name_cpy, '$');
    if (name)
      *name = '\0';
    E155("Could not resolve generic type bound procedure", name_cpy);
  }
  if (GTYPEG(gnr)) {
    ACL *aclp, *hd, *tl;
    /*
     * build the ACL list from the list of arguments
     */
    hd = tl = NULL;
    for (itemp = list; itemp != ITEM_END; itemp = itemp->next) {
      sp = itemp->t.stkp;
      if (SST_IDG(sp) == S_ACONST || SST_IDG(sp) == S_SCONST) {
        aclp = SST_ACLG(sp);
      } else {
        /* put in ACL */
        aclp = GET_ACL(15);
        aclp->id = AC_EXPR;
        aclp->repeatc = aclp->size = 0;
        aclp->next = NULL;
        aclp->subc = NULL;
        aclp->u1.stkp = sp;
      }
      if (!hd) {
        hd = aclp;
      } else {
        tl->next = aclp;
      }
      tl = aclp;
    }
    sptr = GTYPEG(gnr);
    /* create head AC_SCONST for element list */
    aclp = GET_ACL(15);
    aclp->id = AC_SCONST;
    aclp->next = NULL;
    aclp->subc = hd;
    aclp->dtype = DTYPEG(sptr);
    SST_IDP(stktop, S_SCONST);
    SST_DTYPEP(stktop, aclp->dtype);
    SST_ACLP(stktop, aclp);
    chk_struct_constructor(aclp);
    SST_SYMP(stktop, sptr);
    return -1; /* generic resolves to a struct constructor */
  }
  if (CLASSG(gnr)) {
    char *name_cpy, *name;
    name_cpy = getitem(0, strlen(SYMNAME(gnr)) + 1);
    strcpy(name_cpy, SYMNAME(gnr));
    name = strchr(name_cpy, '$');
    if (name)
      *name = '\0';
    E155("Could not resolve generic type bound procedure", name_cpy);
  } else
    E155("Could not resolve generic procedure", SYMNAME(gnr));
  return 0;
}

/*
 * check if arguments passed to a generic match the arguments of the given
 * specific.
 */
static long *
args_match(int ext, int count, int distance_sz, ITEM *list, LOGICAL elemental,
           LOGICAL usedevcopy)
{
  int dscptr;
  int paramct;
  int actual_cnt;
  int i;
  char *kwd_str; /* where keyword string for 'ext' is stored */
  long arg_distance;
  long *distance;

  NEW(distance, long, distance_sz);

  dscptr = DPDSCG(ext);
  paramct = PARAMCTG(ext);

  if (count == 0 && paramct == 0)
    return set_distance_to(MIN_DISTANCE, distance, distance_sz);
  if (count > paramct)
    return set_distance_to(INF_DISTANCE, distance, distance_sz);
  kwd_str = make_kwd_str(ext);
  if (!kwd_match(list, paramct, kwd_str)) {
    FREE(kwd_str);
    return set_distance_to(INF_DISTANCE, distance, distance_sz);
  }
  FREE(kwd_str);

  (void)set_distance_to(MIN_DISTANCE, distance, distance_sz);
  for (i = 0, actual_cnt = 0; i < paramct && actual_cnt < count;
       i++, dscptr++) {
    SST *sp;
    int dum;
    int actual;
    int arg;
    int distance_dx;
    sp = ARG_STK(i);
    if (sp) {
      (void)chkarg(sp, &dum);
      XFR_ARGAST(i);
    }
    actual = ARG_AST(i);
    arg = *(aux.dpdsc_base + dscptr);
    if (arg) {
      if (actual) {
        actual_cnt++;
        arg_distance = tkr_match(arg, sp, actual, elemental);
        if (arg_distance == INF_DISTANCE) {
          return set_distance_to(INF_DISTANCE, distance, distance_sz);
        } else {
          DISTANCE_ELM(distance, i) =
              (DISTANCE_ELM(distance, i) << UNIT_SZ) + arg_distance;
        }
      } else {
        DISTANCE_ELM(distance, i) =
            (DISTANCE_ELM(distance, i) << UNIT_SZ) + MIN_DISTANCE;
      }
    } else if (actual == 0 || A_TYPEG(actual) != A_LABEL) {
      /* alternate returns */
      return set_distance_to(INF_DISTANCE, distance, distance_sz);
    }
  }

  return distance;
}

/* Check TYPE-KIND-RANK */
static int
tkr_match(int formal, SST *opnd, int actual, int elemental)
{
  int ddum, dact, elddum, eldact;
  int rank;
  int sptr;
  LOGICAL match_found;
  int mng_match;
  LOGICAL formal_assumesz = FALSE;

  if (!ignore_tkr(formal, IGNORE_M) && ast_is_sym(actual)) {
    sptr = memsym_of_ast(actual);
    if ( (ALLOCATTRG(formal) && !ALLOCATTRG(sptr)) ||
         (POINTERG(formal) && !POINTERG(sptr)) ) {
      return INF_DISTANCE;
    }
  }

  mng_match = 0;
  ddum = DTYPEG(formal);
  elddum = DDTG(ddum);
  get_type_rank(opnd, &dact, &rank);
  eldact = DDTG(dact);
  if (elemental) {
    dact = eldact;
    rank = 0;
  }
  if (STYPEG(formal) == ST_PROC) {
    if (actual == 0)
      return INF_DISTANCE;
    /* actual must be an ID that is another PROC or ENTRY */
    if (A_TYPEG(actual) != A_ID)
      return INF_DISTANCE;
    sptr = A_SPTRG(actual);
    if (STYPEG(sptr) != ST_PROC && STYPEG(sptr) != ST_ENTRY &&
        !IS_INTRINSIC(STYPEG(sptr)))
      return INF_DISTANCE;
  } else if (A_TYPEG(actual) == A_ID && (STYPEG(A_SPTRG(actual)) == ST_PROC || 
             STYPEG(A_SPTRG(actual)) == ST_ENTRY) && 
             !IS_INTRINSIC(STYPEG(A_SPTRG(actual)))) {
        /* formal is not an ST_PROC, so return INF_DISTANCE */
        return INF_DISTANCE;
  }
  if (!ignore_tkr(formal, IGNORE_R)) {
    if (DTY(ddum) == TY_ARRAY) {
      if (AD_NUMDIM(AD_DPTR(ddum)) != rank) {
        if (rank && AD_ASSUMSZ(AD_DPTR(ddum)) &&
            AD_NUMDIM(AD_DPTR(ddum)) == 1) {
          formal_assumesz = TRUE;
        } else {
          return INF_DISTANCE;
        }
      }
    } else /* formal is not an array */
        if (rank)
      return INF_DISTANCE;
  }

  if (STYPEG(formal) == ST_PROC) {
    if (IS_INTRINSIC(STYPEG(sptr))) {
      setimplicit(sptr);
      dact = DTYPEG(sptr);
      /* TBD: should EXPST be set??? */
    }
    if (ddum == 0) {
      /* formal has no datatype; was the actual really typed? */
      if (DCLDG(sptr) && DTYPEG(sptr)) /* actual was given a datatype */
        return INF_DISTANCE;
      return EXACT_MATCH + mng_match;
    }
    if (dact == 0) {
      /* actual has no datatype; was the formal explicitly typed? */
      if (DCLDG(formal) && DTYPEG(formal)) /* formal was declared */
        return INF_DISTANCE;
      return EXACT_MATCH + mng_match;
    }
    if (!DCLDG(formal) && !FUNCG(formal) && !DCLDG(sptr) && !FUNCG(sptr))
      /* formal & actual are subroutines?? */
      return EXACT_MATCH + mng_match;
  }

  /* check if type and kind of the data types match */
  if (DTY(elddum) != DTY(eldact)) {
    /* element TY_ values are not the same */
    if (ignore_tkr(formal, IGNORE_K)) {
      if (same_type_different_kind(elddum, eldact))
        return EXACT_MATCH + mng_match;
    } else if (ignore_tkr(formal, IGNORE_T) &&
               different_type_same_kind(elddum, eldact))
      return EXACT_MATCH + mng_match;
  }
  if (ignore_tkr(formal, IGNORE_T)) {
    if (ignore_tkr(formal, IGNORE_K))
      return EXACT_MATCH + mng_match;
    /* cannot ignore the kind, so it must be the same! */
    if (different_type_same_kind(elddum, eldact))
      return EXACT_MATCH + mng_match;
  }

  /* check for an exact match first */
  if (tk_match_arg(ddum, dact, FALSE)) {
    return formal_assumesz ? EXTND_MATCH + mng_match : EXACT_MATCH + mng_match;
  } else if (tk_match_arg(ddum, dact, CLASSG(formal))) {
    return EXTND_MATCH + mng_match;
  } else if (DTY(elddum) == TY_DERIVED && UNLPOLYG(DTY(elddum + 3))) {
    /* Dummy argument is declared CLASS(*), so it can
     * take any rank compatible actual argument.
     */
    return formal_assumesz ? EXTND_MATCH + mng_match : EXACT_MATCH + mng_match;
  }
  return INF_DISTANCE;
}

static LOGICAL
kwd_match(ITEM *list,  /* list of arguments */
          int cnt,     /* maximum number of arguments allowed for intrinsic */
          char *kwdarg /* string defining position and keywords of arguments*/
          )
{
  SST *stkp;
  int pos;
  int i;
  char *kwd, *np;
  int kwd_len;
  char *actual_kwd; /* name of keyword used with the actual arg */
  int actual_kwd_len;
  LOGICAL kwd_present;

  /*
   * NOTE:  'variable' arguments (see get_kwd_args in semfunc2.c)
   *        will not be seen for user-defined interfaces.
   */

  kwd_present = FALSE;
  sem.argpos = (argpos_t *)getitem(0, sizeof(argpos_t) * cnt);

  for (i = 0; i < cnt; i++) {
    ARG_STK(i) = NULL;
    ARG_AST(i) = 0;
  }

  for (pos = 0; list != ITEM_END; list = list->next, pos++) {
    stkp = list->t.stkp;
    if (SST_IDG(stkp) == S_KEYWORD) {
      kwd_present = TRUE;
      actual_kwd = scn.id.name + SST_CVALG(stkp);
      actual_kwd_len = strlen(actual_kwd);
      kwd = kwdarg;
      for (i = 0; TRUE; i++) {
#if DEBUG
        assert(*kwd != '#', "kwd_match, unexp. #", pos, 3);
#endif
        if (*kwd == '*')
          kwd++;
        kwd_len = 0;
        for (np = kwd; TRUE; np++, kwd_len++)
          if (*np == ' ' || *np == '\0')
            break;
        if (kwd_len == actual_kwd_len &&
            strncmp(kwd, actual_kwd, actual_kwd_len) == 0)
          break;
        if (*np == '\0')
          return FALSE;
        kwd = np + 1; /* skip over blank */
      }
      if (ARG_STK(i))
        return FALSE;
      stkp = SST_E3G(stkp);
      ARG_STK(i) = stkp;
      ARG_AST(i) = SST_ASTG(stkp);
    } else {
      if (ARG_STK(pos)) {
        kwd = kwdarg;
        for (i = 0; TRUE; i++) {
          if (*kwd == '*' || *kwd == ' ')
            kwd++;
          if (*kwd == '\0')
            return FALSE;
          kwd_len = 0;
          for (np = kwd; TRUE; np++) {
            if (*np == ' ' || *np == '\0')
              break;
            kwd_len++;
          }
          if (i == pos)
            break;
          kwd = np;
        }
        return FALSE;
      }
      ARG_STK(pos) = stkp;
      ARG_AST(pos) = SST_ASTG(stkp);
    }
  }

  /* determine if required argument is not present */

  kwd = kwdarg;
  for (pos = 0; pos < cnt; pos++, kwd = np) {
    if (*kwd == ' ')
      kwd++;
    if (*kwd == '#' || *kwd == '!')
      break;
    kwd_len = 0;
    for (np = kwd; TRUE; np++) {
      if (*np == ' ' || *np == '\0')
        break;
      kwd_len++;
    }
    if (*kwd == '*')
      continue;
    if (ARG_STK(pos) == NULL)
      return FALSE;
  }

  return TRUE;
}

int
defined_operator(int opr, SST *stktop, SST *lop, SST *rop)
{
  int sptr;
  ITEM *list;
  int i;

#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user operator %s\n", SYMNAME(opr));
#endif
  if (queue_generic_tbp_once(0))
    queue_tbp(0, 0, 0, 0, TBP_COMPLETE_GENERIC);
  if (STYPEG(opr) != ST_OPERATOR) {
    i = findByNameStypeScope(SYMNAME(opr), ST_OPERATOR, stb.curr_scope);
    if (i) {
      opr = i;
    }
  }
  sptr = resolve_operator(opr, lop, rop);
  if (sptr == 0) {
    SST_IDP(stktop, S_CONST);
    SST_DTYPEP(stktop, DT_INT);
    return 1;
  }
#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "user operator resolved to %s\n", SYMNAME(sptr));
#endif

  list = make_list(lop, rop);
  mkident(stktop);
  SST_SYMP(stktop, sptr);
  SST_DTYPEP(stktop, DTYPEG(sptr));
  SST_ASTP(stktop, mk_id(sptr));
  return func_call2(stktop, list, 1);
}

static int
resolve_operator(int opr, SST *lop, SST *rop)
{
  int func;
#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "resolve_operator: %s, count %d\n", SYMNAME(opr),
            rop == NULL ? 1 : 2);
#endif
  func = find_operator(opr, lop, rop, FALSE);
  if (func != 0) {
    return func;
  }
  /* Redo the search, this time allow type matching for elemental subprograms */
  func = find_operator(opr, lop, rop, TRUE);
  if (func != 0) {
    return func;
  }

  /* Overloading did not occur; issue error message only if this is not
   * an intrinsic operator.
   */
  if (INKINDG(opr) == 0) {
    if (GNCNTG(opr) == 0) {
      E155("Empty operator -", SYMNAME(opr));
    } else {
      E155("Could not resolve operator", SYMNAME(opr));
    }
  }
  return 0;
}

static int
find_operator(int opr, SST *lop, SST *rop, LOGICAL elemental)
{
  int sptr;
  int opnd_cnt = rop == NULL ? 1 : 2;
  int nmptr = NMPTRG(opr);
  for (sptr = first_hash(opr); sptr; sptr = HASHLKG(sptr)) {
    int gndsc;
    int sptrgen = sptr;
    if (NMPTRG(sptrgen) != nmptr)
      continue;
    if (STYPEG(sptrgen) == ST_ALIAS)
      sptrgen = SYMLKG(sptrgen);
    if (STYPEG(sptrgen) != ST_OPERATOR)
      continue;
    /* is the ST_OPERATOR or ST_ALIAS in an active scope */
    if (test_scope(sptr) < 0 && !CLASSG(sptrgen))
      continue;

    for (gndsc = GNDSCG(sptrgen); gndsc; gndsc = SYMI_NEXT(gndsc)) {
      int dscptr;
      int paramct;
      int bind;
      int func = SYMI_SPTR(gndsc);
      if (IS_TBP(func)) {
        /* For generic type bound procedures, use the implementation
         * of the generic bind name for the argument comparison.
         */
        int mem, dty;
        bind = func;
        dty = TBPLNKG(func);
        func = get_implementation(dty, func, 0, &mem);
        if (STYPEG(BINDG(mem)) == ST_OPERATOR ||
            STYPEG(BINDG(mem)) == ST_USERGENERIC) {
          mem = get_specific_member(dty, func);
          func = VTABLEG(mem);
          bind = BINDG(mem);
        }
        if (!func)
          continue;
        mem = get_generic_member(dty, bind);
        if (mem && PRIVATEG(mem) && SCOPEG(stb.curr_scope) != SCOPEG(mem))
          continue;
      } else {
        bind = 0;
      }
      if (STYPEG(func) == ST_MODPROC) {
        func = SYMLKG(func);
        if (func == 0)
          continue;
      }
      if (STYPEG(func) == ST_ALIAS)
        func = SYMLKG(func);
      paramct = PARAMCTG(func);

      if (paramct != opnd_cnt) {
        if (!bind) {
          continue;
        } else {
          dscptr = DPDSCG(func);
          if (paramct == 2 && opnd_cnt == 1) {
            int arg = *(aux.dpdsc_base + dscptr + 1);
            if (!CCSYMG(arg) || !CLASSG(arg))
              continue;
          } else if (paramct == 4 && opnd_cnt == 2) {
            int arg = *(aux.dpdsc_base + dscptr + 2);
            if (!CCSYMG(arg) || !CLASSG(arg))
              continue;
            arg = *(aux.dpdsc_base + dscptr + 3);
            if (!CCSYMG(arg) || !CLASSG(arg))
              continue;
          } else {
            continue;
          }
        }
      }
      dscptr = DPDSCG(func);
      if (!elemental || ELEMENTALG(func)) {
        int arg = *(aux.dpdsc_base + dscptr);
        if (arg && (tkr_match(arg, lop, 0, elemental) == INF_DISTANCE))
          continue;
        if (rop != NULL) {
          int arg = *(aux.dpdsc_base + dscptr + 1);
          if (arg && (tkr_match(arg, rop, 0, elemental) == INF_DISTANCE))
            continue;
        }
        return bind ? bind : func;
      }
    }
  }
  return 0; // not found
}

void
init_intrinsic_opr(void)
{
  int i;

  for (i = 0; i <= OP_VAL; i++)
    optab[i].opr = 0;
}

void
bind_intrinsic_opr(int val, int opr)
{
  optab[val].opr = opr;
  INKINDP(opr, 1);  /* intrinsic or assignment operator */
  PDNUMP(opr, val); /* OP_... value */
}

static int
tkn_alias_sym(int tkn_alias)
{
  int sym;
  switch (tkn_alias) {
  case TK_XORX:
    sym = getsymbol("x");
    break;
  case TK_XOR:
    sym = getsymbol("xor");
    break;
  case TK_ORX:
    sym = getsymbol("o");
    break;
  case TK_NOTX:
    sym = getsymbol("n");
    break;
  default:
    interr("tkn_alias_sym: no token", 0, 3);
    sym = getsymbol("..zz");
  }
  return sym;
}

int
get_intrinsic_oprsym(int val, int tkn_alias)
{
  int sym;
  if (!tkn_alias)
    sym = getsymbol(optab[val].name);
  else
    sym = tkn_alias_sym(tkn_alias);
  return sym;
}

int
get_intrinsic_opr(int val, int tkn_alias)
{
  int opr;
  opr = get_intrinsic_oprsym(val, tkn_alias);
  opr = declsym(opr, ST_OPERATOR, FALSE);
  bind_intrinsic_opr(val, opr);

  return opr;
}

LOGICAL
is_intrinsic_opr(int val, SST *stktop, SST *lop, SST *rop, int tkn_alias)
{
  /*  tkn_alias is currently not referenced */
  int opr;
  int func;
  ITEM *list;
  int rank, dtype;
  char buf[100];

  opr = optab[val].opr;
  if (opr) {
    func = resolve_operator(opr, lop, rop);
    if (!func && /*IN_MODULE*/ sem.mod_cnt && sem.which_pass) {
      if (queue_generic_tbp_once(0))
        queue_tbp(0, 0, 0, 0, TBP_COMPLETE_GENERIC);
      func = resolve_operator(opr, lop, rop);
    }
    if (CLASSG(func) && IS_TBP(func)) {
      int ast, mem, inv;
      get_implementation(TBPLNKG(func), func, 0, &mem);
      if (NOPASSG(mem)) {
        if (val != OP_ST) {
          E155("Type bound procedure with NOPASS attribute not valid "
               "for generic operator",
               SYMNAME(opr));
        } else {
          E155("Type bound procedure with NOPASS attribute not valid "
               "for generic assignment",
               SYMNAME(opr));
        }
        inv = 0;
      } else {
        inv = get_tbp_argno(func, TBPLNKG(func));
      }
      if (inv < 1 || inv > 2) {
        if (val != OP_ST) {
          E155("Invalid type bound procedure in generic set "
               "for generic operator",
               SYMNAME(opr));
        } else {
          E155("Invalid type bound procedure in generic set "
               "for generic assignment",
               SYMNAME(opr));
        }
        inv = 0;
      }
      list = make_list(lop, rop);
      if (rop != NULL && (inv == 1 || inv == 2)) {
        if (SST_IDG(rop) == S_SCONST) {
          /* Support operator look up with structure
           * constructor argument on RHS.
           */
          int tmp = getccsym_sc('d', sem.dtemps++, ST_VAR, SC_LOCAL);
          DTYPEP(tmp, SST_DTYPEG(rop));
          ast = mk_id(tmp);
        } else if (inv == 1) {
          mkexpr(lop);
          ast = SST_ASTG(lop);
          if (A_TYPEG(ast) == A_INTR) {
            mkexpr(rop);
            ast = SST_ASTG(rop);
          }

        } else {
          mkexpr(rop);
          ast = SST_ASTG(rop);
          if (A_TYPEG(ast) == A_INTR) {
            mkexpr(lop);
            ast = SST_ASTG(lop);
          }
        }
      } else {
        mkexpr(lop);
        ast = SST_ASTG(lop);
      }
      ast = mkmember(TBPLNKG(func), ast, NMPTRG(mem));
      SST_ASTP(stktop, ast);
      SST_SYMP(stktop, -func);
      if (val == OP_ST)
        subr_call2(stktop, list, 1);
      else
        func_call2(stktop, list, 1);
      return TRUE;
    }
    if (func != 0) {
#if DEBUG
      if (DBGBIT(3, 256))
        fprintf(gbl.dbgfil, "intrinsic operator resolved to %s\n",
                SYMNAME(func));
#endif
      list = make_list(lop, rop);
      mkident(stktop);
      SST_SYMP(stktop, -func);
      if (val == OP_ST)
        subr_call2(stktop, list, 1);
      else {
        SST_ASTP(stktop, mk_id(func));
        SST_DTYPEP(stktop, DTYPEG(func));
        func_call2(stktop, list, 1);
      }
      return TRUE;
    }
  }

  /* Check for illegal use of an operator on a derived type. */
  if (val == OP_ST) /* Assignment is ok. */
    return FALSE;
  get_type_rank(lop, &dtype, &rank);
  if (DTYG(dtype) == TY_DERIVED) {
    /*
     * (reference f20848) - long ago, semgnr.c spelled .ne. as "!=".
     * As a consequence, operator(/=) would show up as != in the
     * symbol table and propagated to .mod files, such as
     * iso_c_binding.  Fixing semgnr means that we will fail to
     * process '!=' from mod files; interf.c needs to change '!=' to '/=';
     * and the mod file version needs to incremented.  SO, just hack the
     * error message when appropriate.
     */
    if (strcmp(optab[val].name, "!="))
      sprintf(buf, "operator %s on a derived type", optab[val].name);
    else
      sprintf(buf, "operator %s on a derived type", "/=");
    error(99, 3, gbl.lineno, buf, CNULL);
  } else if (rop != NULL) {
    get_type_rank(rop, &dtype, &rank);
    if (DTYG(dtype) == TY_DERIVED) {
      if (strcmp(optab[val].name, "!="))
        sprintf(buf, "operator %s on a derived type", optab[val].name);
      else
        sprintf(buf, "operator %s on a derived type", "/=");
      error(99, 3, gbl.lineno, buf, CNULL);
    }
  }
  return FALSE;
}

static void
get_type_rank(SST *stkptr, int *dt_p, int *rank_p)
{
  int dtype;
  int sptr;
  int shape;

  dtype = 0;
  shape = 0;
  switch (SST_IDG(stkptr)) {
  case S_IDENT:
    sptr = SST_SYMG(stkptr);
    switch (STYPEG(sptr)) {
    case ST_INTRIN:
    case ST_GENERIC:
    case ST_PD:
      if (!EXPSTG(sptr)) {
        /* Not a frozen intrinsic, so assume its a variable */
        sptr = newsym(sptr);
        STYPEP(sptr, ST_VAR);
        /* need storage class (local) */
        sem_set_storage_class(sptr);
        SST_SYMP(stkptr, sptr);
        dtype = DTYPEG(sptr);
      }
      break;
    case ST_UNKNOWN:
    case ST_IDENT:
    case ST_VAR:
    case ST_ARRAY:
    case ST_STRUCT:
    case ST_ENTRY:
    case ST_USERGENERIC:
    case ST_PROC:
      dtype = DTYPEG(sptr);
      break;
    default:
      break;
    }
    break;
  case S_LVALUE:
  case S_LOGEXPR:
  case S_EXPR:
    dtype = SST_DTYPEG(stkptr);
    shape = SST_SHAPEG(stkptr);
    break;
  case S_CONST:
  case S_SCONST:
  case S_ACONST:
    dtype = SST_DTYPEG(stkptr);
    break;
  case S_STFUNC:
  case S_DERIVED:
    dtype = DTYPEG(SST_SYMG(stkptr));
    break;
  default:
    break;
  }

  *dt_p = dtype;
  *rank_p = 0;

  if (dtype) {
    if (shape)
      *rank_p = SHD_NDIM(shape);
    else if (DTY(dtype) == TY_ARRAY)
      *rank_p = AD_NUMDIM(AD_DPTR(dtype));
  }

}

static ITEM *
make_list(SST *lop, SST *rop)
{
  ITEM *list;

  list = (ITEM *)getitem(0, sizeof(ITEM));
  list->t.stkp = (SST *)getitem(0, sizeof(SST));
  *list->t.stkp = *lop;

  if (rop != NULL) {
    ITEM *tmp;
    tmp = (ITEM *)getitem(0, sizeof(ITEM));
    tmp->t.stkp = (SST *)getitem(0, sizeof(SST));
    *tmp->t.stkp = *rop;
    list->next = tmp;
    tmp->next = ITEM_END;
  } else
    list->next = ITEM_END;

  return list;
}

void rw_gnr_state(RW_ROUTINE, RW_FILE)
{
  int nw;
  RW_FD(optab, struct optabstruct, OPTABSIZE);
} /* rw_gnr_state */

static void
defined_io_error(char *proc, int is_unformatted, char *msg, int func)
{

  char *buf;

  buf = getitem(0, strlen("for defined WRITE(UNFORMATTED), in subroutine") +
                       strlen(msg) + 1);
  sprintf(buf, "for defined %s(%s), %s in subroutine",
          (strcmp(proc, ".read") == 0) ? "READ" : "WRITE",
          (is_unformatted) ? "UNFORMATTED" : "FORMATTED", msg);

  error(155, 3, gbl.lineno, buf, SYMNAME(func));
}

static void
check_defined_io2(char *proc, int silentmode, int chk_dtype)
{
  int gnr, sptr, sptrgen;
  LOGICAL gnr_in_active_scope;
  int gn_cnt;
  int gndsc, nmptr;
  int func, paramct, dpdsc, iface, i;
  int psptr, dtype, tag;
  int mem, is_unformatted, func2;
  int seen_error, dtv_dtype;
  int extensible, found;
  int bind, dt_int;
  int second_arg_error;

  if (!proc)
    return;
  if (XBIT(124, 0x10)) {
    dt_int = DT_INT8; /* -i8 */
  } else {
    dt_int = DT_INT;
  }
  if (chk_dtype) {
    if (DTY(chk_dtype) == TY_ARRAY)
      chk_dtype = DTY(chk_dtype + 1);
    if (DTY(chk_dtype) != TY_DERIVED)
      return;
  }
  gnr = getsymbol(proc);
  found = 0;
  if (STYPEG(gnr) == ST_USERGENERIC) {
    gnr_in_active_scope = FALSE;
    nmptr = NMPTRG(gnr);
    for (sptr = first_hash(gnr); sptr > NOSYM; sptr = HASHLKG(sptr)) {
      sptrgen = sptr;
      second_arg_error = seen_error = 0;
      dtv_dtype = 0;
      extensible = 0;
      if (NMPTRG(sptrgen) != nmptr)
        continue;
      if (STYPEG(sptrgen) == ST_ALIAS)
        sptrgen = SYMLKG(sptrgen);
      if (STYPEG(sptrgen) != ST_USERGENERIC)
        continue;
      /* is the original symbol (sptr, not sptrgen) in an active scope */
      if (test_scope(sptr)) {
        gnr_in_active_scope = TRUE;
      }
      if (!gnr_in_active_scope && !CLASSG(sptrgen))
        continue;
      if (GNCNTG(sptrgen) == 0 && GTYPEG(sptrgen))
        continue;
      if (queue_generic_tbp_once(sptrgen)) {
        queue_tbp(0, 0, 0, 0, TBP_COMPLETE_GENERIC);
      }

      for (gndsc = GNDSCG(sptrgen); gndsc; gndsc = SYMI_NEXT(gndsc)) {
        func = SYMI_SPTR(gndsc);
        is_unformatted = 0;

        if (IS_TBP(func)) {
          /* For generic type bound procedures, use the implementation
           * of the generic bind name for the argument comparison.
           */
          int mem, dty;
          bind = func;
          dty = TBPLNKG(func);

          func = get_implementation(dty, func, 0, &mem);
          if (STYPEG(BINDG(mem)) == ST_OPERATOR ||
              STYPEG(BINDG(mem)) == ST_USERGENERIC) {
            mem = get_specific_member(dty, func);
            func = VTABLEG(mem);
            bind = BINDG(mem);
          }
          if (!func)
            continue;
          mem = get_generic_member(dty, bind);
          if (NOPASSG(mem) && generic_tbp_has_pass_and_nopass(dty, BINDG(mem)))
            continue;
          if (mem && PRIVATEG(mem) && SCOPEG(stb.curr_scope) != SCOPEG(mem))
            continue;
        } else
          bind = 0;

        for (func2 = (!bind) ? first_hash(func) : first_hash(bind);
             func2 > NOSYM; func2 = HASHLKG(func2)) {
          if (!test_scope(func2))
            continue;
          if (UNFMTG(func2)) {
            is_unformatted = 1;
            break;
          }
        }
        if (FVALG(func)) {
          seen_error++;
          if (!silentmode) {
            if (is_unformatted) {
              if (strcmp(proc, ".read") == 0) {
                error(155, 3, gbl.lineno,
                      "The generic set for a defined"
                      "READ(UNFORMATTED) contains non-subroutine",
                      SYMNAME(func));
              } else {
                error(155, 3, gbl.lineno,
                      "The generic set for a defined"
                      "WRITE(UNFORMATTED) contains non-subroutine",
                      SYMNAME(func));
              }
            } else {
              if (strcmp(proc, ".read") == 0) {
                error(155, 3, gbl.lineno,
                      "The generic set for a defined"
                      "READ(FORMATTED) contains non-subroutine",
                      SYMNAME(func));
              } else {
                error(155, 3, gbl.lineno,
                      "The generic set for a defined"
                      "WRITE(FORMATTED) contains non-subroutine",
                      SYMNAME(func));
              }
            }
          }
          continue;
        }
        paramct = dpdsc = iface = 0;
        if (STYPEG(func) == ST_MODPROC) {
          func = SYMLKG(func);
          if (func <= NOSYM)
            continue;
        }
        if (STYPEG(func) == ST_ALIAS) {
          func = SYMLKG(func);
          if (func <= NOSYM)
            continue;
        }
        if (STYPEG(func) != ST_PROC && STYPEG(func) != ST_ENTRY)
          continue;

        proc_arginfo(func, &paramct, &dpdsc, &iface);
        if (!dpdsc)
          continue;

        if (paramct > 4) {
          psptr = *(aux.dpdsc_base + dpdsc + (paramct - 1));
          if (CLASSG(psptr) && CCSYMG(psptr)) {
            --paramct; /* don't count type descriptor arg */
          }
        }

        if (is_unformatted && paramct == 4) {
          psptr = *(aux.dpdsc_base + dpdsc);
          dtype = DTYPEG(psptr);
          if (DTY(dtype) == TY_ARRAY)
            dtype = DTY(dtype + 1);
          if (DTY(dtype) != TY_DERIVED) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "first argument must be a derived type", func);
            continue;
          }
          dtv_dtype = dtype;
          tag = DTY(dtype + 3);
          if (!CLASSG(psptr) && !CFUNCG(tag) && !SEQG(tag)) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "first argument with extensible type"
                               " must be declared CLASS",
                               func);
          }
          if (CLASSG(psptr) && !CFUNCG(tag) && !SEQG(tag)) {
            extensible = 1;
          }
          if (!all_len_parms_assumed(dtype)) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "all length type parameters must be assumed"
                               " for derived type argument 1",
                               func);
          }
          if (INTENTG(psptr) != INTENT_INOUT && INTENTG(psptr) != INTENT_IN) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "first argument must be declared INTENT(IN)"
                               " or INTENT(INOUT)",
                               func);
          }

          psptr = *(aux.dpdsc_base + dpdsc + 1);
          dtype = DTYPEG(psptr);
          if (DT_ISINT(dtype)) {
            dt_int = dtype;
          }
          if (dtype != dt_int) {
            seen_error++;
            second_arg_error = 1;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "second argument must be declared INTEGER",
                               func);
          }
          if (INTENTG(psptr) != INTENT_IN) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "second argument must be declared"
                               " INTENT(IN)",
                               func);
          }
          psptr = *(aux.dpdsc_base + dpdsc + 2);
          dtype = DTYPEG(psptr);
          if (dtype != dt_int) {
            seen_error++;
            if (!silentmode) {
              if (second_arg_error) {
                defined_io_error(proc, is_unformatted,
                                 "third argument must be declared INTEGER",
                                 func);
              } else {
                defined_io_error(proc, is_unformatted,
                                 "second and third argument must be declared "
                                 "INTEGER",
                                 func);
              }
            }
          }
          if (INTENTG(psptr) != INTENT_OUT) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "third argument must be declared "
                               "INTENT(INOUT)",
                               func);
          }
          psptr = *(aux.dpdsc_base + dpdsc + 3);
          dtype = DTYPEG(psptr);
          if (dtype != DT_ASSCHAR) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "fourth argument must be declared "
                               "CHARACTER(LEN=*)",
                               func);
          }
          if (INTENTG(psptr) != INTENT_INOUT) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "fourth argument must be declared "
                               "INTENT(INOUT)",
                               func);
          }
          if (!seen_error) {
            /* set UFIO flag on the tag */
            if (strcmp(proc, ".read") == 0) {
              UFIOP(tag, (DT_IO_UREAD | UFIOG(tag)));
            } else {
              UFIOP(tag, (DT_IO_UWRITE | UFIOG(tag)));
            }
            if (chk_dtype && eq_dtype2(dtv_dtype, chk_dtype, extensible)) {
              int tag2;
              tag2 = DTY(chk_dtype + 3);
              found++;
              if (strcmp(proc, ".read") == 0) {
                UFIOP(tag2, (DT_IO_UREAD | UFIOG(tag2)));
              } else {
                UFIOP(tag2, (DT_IO_UWRITE | UFIOG(tag2)));
              }
              UFIOP(tag2, (UFIOG(tag2) & ~(DT_IO_NONE)));
            }
          }
        } else if (!is_unformatted && paramct == 6) {
          psptr = *(aux.dpdsc_base + dpdsc);
          dtype = DTYPEG(psptr);
          if (DTY(dtype) == TY_ARRAY)
            dtype = DTY(dtype + 1);
          if (DTY(dtype) != TY_DERIVED) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "first argument must be a derived type", func);
            continue;
          }
          dtv_dtype = dtype;
          tag = DTY(dtype + 3);
          if (!CLASSG(psptr) && !CFUNCG(tag) && !SEQG(tag)) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "first argument with extensible type"
                               " must be declared CLASS",
                               func);
          }
          if (CLASSG(psptr) && !CFUNCG(tag) && !SEQG(tag)) {
            extensible = 1;
          }
          if (!all_len_parms_assumed(dtype)) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "all length type parameters must be assumed"
                               " for derived type argument 1",
                               func);
          }
          if (INTENTG(psptr) != INTENT_INOUT && INTENTG(psptr) != INTENT_IN) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "first argument must be declared INTENT(IN)"
                               " or INTENT(INOUT)",
                               func);
          }

          psptr = *(aux.dpdsc_base + dpdsc + 1);
          dtype = DTYPEG(psptr);
          if (DT_ISINT(dtype)) {
            dt_int = dtype;
          }
          if (dtype != dt_int) {
            seen_error++;
            second_arg_error = 1;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "second argument must be declared INTEGER",
                               func);
          }
          if (INTENTG(psptr) != INTENT_IN) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "second argument must be declared"
                               " INTENT(IN)",
                               func);
          }
          psptr = *(aux.dpdsc_base + dpdsc + 2);
          dtype = DTYPEG(psptr);
          if (dtype != DT_ASSCHAR) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "third argument must be declared "
                               "CHARACTER(LEN=*)",
                               func);
          }
          if (INTENTG(psptr) != INTENT_IN) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "third argument must be declared INTENT(IN)",
                               func);
          }
          psptr = *(aux.dpdsc_base + dpdsc + 3);
          dtype = DTYPEG(psptr);
          if (DTY(dtype) != TY_ARRAY || DTY(dtype + 1) != dt_int ||
              !ASSUMSHPG(psptr) || rank_of_sym(psptr) != 1) {
            seen_error++;
            if (!silentmode) {
              if (!second_arg_error) {
                defined_io_error(proc, is_unformatted,
                                 "second argument must be declared INTEGER",
                                 func);
              }
              defined_io_error(proc, is_unformatted,
                               "fourth argument must be a rank 1 assumed"
                               " shape array of type INTEGER",
                               func);
            }
          }
          if (INTENTG(psptr) != INTENT_IN) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "fourth argument must be declared INTENT(IN)",
                               func);
          }
          psptr = *(aux.dpdsc_base + dpdsc + 4);
          dtype = DTYPEG(psptr);
          if (dtype != dt_int) {
            seen_error++;
            if (!silentmode) {
              if (second_arg_error) {
                defined_io_error(proc, is_unformatted,
                                 "fifth argument must be declared INTEGER",
                                 func);
              } else {
                defined_io_error(proc, is_unformatted,
                                 "second and fifth argument must be declared "
                                 "INTEGER",
                                 func);
              }
            }
          }
          if (INTENTG(psptr) != INTENT_OUT) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "fifth argument must be declared "
                               "INTENT(OUT)",
                               func);
          }
          psptr = *(aux.dpdsc_base + dpdsc + 5);
          dtype = DTYPEG(psptr);
          if (dtype != DT_ASSCHAR) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "sixth argument must be declared "
                               "CHARACTER(LEN=*)",
                               func);
          }
          if (INTENTG(psptr) != INTENT_INOUT) {
            seen_error++;
            if (!silentmode)
              defined_io_error(proc, is_unformatted,
                               "sixth argument must be declared "
                               "INTENT(INOUT)",
                               func);
          }

          if (!seen_error) {
            /* set UFIO flag on the tag */
            if (strcmp(proc, ".read") == 0) {
              UFIOP(tag, (DT_IO_FREAD | UFIOG(tag)));
            } else {
              UFIOP(tag, (DT_IO_FWRITE | UFIOG(tag)));
            }
            if (chk_dtype && eq_dtype2(dtv_dtype, chk_dtype, extensible)) {
              int tag2;
              tag2 = DTY(chk_dtype + 3);
              found++;
              if (strcmp(proc, ".read") == 0) {
                UFIOP(tag2, (DT_IO_FREAD | UFIOG(tag2)));
              } else {
                UFIOP(tag2, (DT_IO_FWRITE | UFIOG(tag2)));
              }
              UFIOP(tag2, (UFIOG(tag2) & ~(DT_IO_NONE)));
            }
          }
        } else {
          seen_error++;
          if (!silentmode)
            defined_io_error(proc, is_unformatted, "invalid argument list",
                             func);
        }
      }
    }
  }
  if (!found && chk_dtype) {
    tag = DTY(chk_dtype + 3);
    if (!UFIOG(tag)) {
      UFIOP(tag, DT_IO_NONE);
    }
  }
}

/** \brief Return a bit mask indicating which I/O routines are defined for a
           derived type.
 */
int
dtype_has_defined_io(int dtype)
{
  int tag;

  if (DTY(dtype) == TY_ARRAY)
    dtype = DTY(dtype + 1);
  if (DTY(dtype) != TY_DERIVED)
    return 0;

  tag = DTY(dtype + 3);

  if (!UFIOG(tag)) {
    check_defined_io2(".read", 1, dtype);
    check_defined_io2(".write", 1, dtype);
  }
  return UFIOG(tag);
}

void
check_defined_io(void)
{

  check_defined_io2(".write", 0, 0);
  check_defined_io2(".read", 0, 0);
}

/**
   \param read_or_write  0 specifies read, 1 specifies write
   \param stktop         SST we're processing.
   \param list           argument list for read/write
   \return
   <pre>
   = -1 : error (resolves to struct constructor -- should never happen)
   = 0  : error or no I/O subroutine
   \> 0  : sptr of the 'specific' defined I/O subroutine
   </pre>
 */
int
resolve_defined_io(int read_or_write, SST *stktop, ITEM *list)
{
  int i;
  int gnr = getsymbol(read_or_write ? ".write" : ".read");

  if (STYPEG(gnr) != ST_USERGENERIC) {
    return 0;
  }

  resolved_to_self = 0;
  silent_error_mode = 1;
  i = resolve_generic(gnr, stktop, list);
  silent_error_mode = 0;
  if (resolved_to_self) {
    if (i > NOSYM && !RECURG(gbl.currsub)) {
      error(155, 3, gbl.lineno,
            "Subroutines that participate in recursive"
            " defined I/O operations must be declared RECURSIVE -",
            SYMNAME(gbl.currsub));
    }
    resolved_to_self = 0;
  }
  return i;
}

void
add_overload(int gnr, int func)
{
  int gnidx;
  if (sem.defined_io_type == 2 || sem.defined_io_type == 4) {
    UNFMTP(func, 1);
  }
  gnidx = add_symitem(func, GNDSCG(gnr));
  GNDSCP(gnr, gnidx);
  GNCNTP(gnr, GNCNTG(gnr) + 1);
#if DEBUG
  if (DBGBIT(3, 256))
    fprintf(gbl.dbgfil, "overload %s --> %s, symi_base+%d\n", SYMNAME(gnr),
            SYMNAME(func), gnidx);
#endif
}

void
copy_specifics(int fromsptr, int tosptr)
{
  int symi_src;

  assert((STYPEG(fromsptr) == ST_OPERATOR || STYPEG(fromsptr) == ST_USERGENERIC) &&
         (STYPEG(tosptr) == ST_OPERATOR || STYPEG(tosptr) == ST_USERGENERIC),
         "copy_specifics src or dest not user generic or operator", 0, 3);

  for (symi_src = GNDSCG(fromsptr); symi_src; symi_src = SYMI_NEXT(symi_src)) {
    /* don't copy if the specific is already in the generic's list */
    /* TODO: is comparison of sptrs good enough or is comparison
     * of nmptr and signature necessary?
     */
    int src = SYMI_SPTR(symi_src);
    if (!sym_in_sym_list(src, GNDSCG(tosptr))) {
      add_overload(tosptr, src);
    }
  }
}
